In an LPDA auction, the price falls from startPrice down to startPrice - (dropPerSecond * (endTime - startTime). At this point, the auction continues, but the price no longer falls.
If the auction stops at a price that doesn't clear the market, it'll remain open indefinitely.
But, as long as the auction remains open, the owners are not able to withdraw their funds and the feeReceiver doesn't receive their fees.
The result is that an NFT collection can be mostly minted and active, but the owners are not able to collect their earnings to fund the project.
Proof of Concept
The only place in the LPDA.sol contract where funds are transferred to the feeReceiver and saleReceiver are in the final block of the buy() function:
For this block to execute, the NFTs need to be minted up to the point where newId == sale.finalId. If this doesn't happen, this block can never execute.
There are only two ways for this to happen:
1) cancel() is called, which can only happen before a sale starts.
2) Enough NFTs are minted that currentId increases to the point that currentId + amount reaches finalId.
If the latter doesn't happen, the funds will be stuck.
[Note that this doesn't impact refunds, as users will be able to call refund() at any time to get their refund up to the current price.]
Tools Used
Manual Review
Recommended Mitigation Steps
Add an end() function that can be called after the endTime to finalize the LPDA and process the payouts.
Lines of code
https://github.com/code-423n4/2022-12-escher/blob/5d8be6aa0e8634fdb2f328b99076b0d05fefab73/src/minters/LPDA.sol#L81-L88
Vulnerability details
Impact
In an LPDA auction, the price falls from
startPrice
down tostartPrice - (dropPerSecond * (endTime - startTime)
. At this point, the auction continues, but the price no longer falls.If the auction stops at a price that doesn't clear the market, it'll remain open indefinitely.
But, as long as the auction remains open, the owners are not able to withdraw their funds and the feeReceiver doesn't receive their fees.
The result is that an NFT collection can be mostly minted and active, but the owners are not able to collect their earnings to fund the project.
Proof of Concept
The only place in the LPDA.sol contract where funds are transferred to the
feeReceiver
andsaleReceiver
are in the final block of thebuy()
function:For this block to execute, the NFTs need to be minted up to the point where
newId == sale.finalId
. If this doesn't happen, this block can never execute.There are only two ways for this to happen:
1)
cancel()
is called, which can only happen before a sale starts. 2) Enough NFTs are minted that currentId increases to the point that currentId + amount reaches finalId.If the latter doesn't happen, the funds will be stuck.
[Note that this doesn't impact refunds, as users will be able to call
refund()
at any time to get their refund up to the current price.]Tools Used
Manual Review
Recommended Mitigation Steps
Add an
end()
function that can be called after theendTime
to finalize the LPDA and process the payouts.