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: 86/119
Findings: 2
Award: $2.18
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: adriro
Also found by: 0x446576, 0xA5DF, 0xDave, 0xDecorativePineapple, 0xRobocop, 0xbepresent, 8olidity, Aymen0909, Ch_301, Chom, Franfran, HollaDieWaldfee, Madalad, Parth, Ruhum, Tricko, bin2chen, carrotsmuggler, chaduke, danyams, evan, gz627, hansfriese, hihen, imare, immeas, jadezti, jayphbee, jonatascm, kaliberpoziomka8552, kiki_dev, kree-dotcom, ladboy233, lukris02, lumoswiz, mahdikarimi, minhquanym, minhtrng, nameruse, neumo, obront, pauliax, poirots, reassor, rvierdiiev, slvDev, sorrynotsorry, yixxas, zapaz
0.8413 USDC - $0.84
https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L124
In LPDA, price is calculated as: price = sale.startPrice - (sale.dropPerSecond * timeElapsed)
. if sale.dropPerSecond > sale.startPrice/(sale.endTime - sale.startTime)
, the price
will drop to negative before sale.endTime
arrives. If the amount of sold items has not reached the amount of auction items before sale.price
drops to negative, the auction will fail and will never end. This is because when calculating the sale.price
, it will underflow, leading to transaction revert.
In the below test case, the 2nd purchase will revert since the sale.price
has dropped to negative value at this time.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import "forge-std/Test.sol"; import {EscherTest} from "./utils/EscherTest.sol"; import {LPDAFactory, LPDA} from "src/minters/LPDAFactory.sol"; contract LPDAPriceDropTooFastBase is EscherTest { LPDAFactory public lpdaSales; LPDA.Sale public lpdaSale; function setUp() public virtual override { super.setUp(); lpdaSales = new LPDAFactory(); // set up a LPDA Sale lpdaSale = LPDA.Sale({ currentId: uint48(0), finalId: uint48(10), edition: address(edition), startPrice: uint80(uint256(1 ether)), finalPrice: uint80(uint256(0.1 ether)), dropPerSecond: uint80(uint256(1 ether) / 10 hours), startTime: uint96(block.timestamp), saleReceiver: payable(address(69)), endTime: uint96(block.timestamp + 1 days) }); } } contract LPDAPriceDropTooFastTest is LPDAPriceDropTooFastBase { LPDA public sale; function test_PriceDropTooFast_LPDA() public { // make the lpda sales contract sale = LPDA(lpdaSales.createLPDASale(lpdaSale)); // authorize the lpda sale to mint tokens edition.grantRole(edition.MINTER_ROLE(), address(sale)); // 1st buy success sale.buy{value: 1 ether}(1); assertEq(address(sale).balance, 1 ether); vm.warp(block.timestamp + 12 hours); vm.expectRevert(); // 2nd purchase revert due to sale.price underflow sale.buy{value: uint256((1 ether) * 8)}(8); } }
Manual audit.
Add prameter verification to make sure sale.price > 0
at sale.endTime
, i.e. to make sure sale.dropPerSecond < sale.startPrice/(sale.endTime - sale.startTime)
.
#0 - c4-judge
2022-12-13T11:53:20Z
berndartmueller marked the issue as duplicate of #392
#1 - c4-judge
2023-01-02T19:57:12Z
berndartmueller marked the issue as satisfactory
🌟 Selected for report: AkshaySrivastav
Also found by: 0x52, 0xA5DF, 0xdeadbeef0x, KingNFT, Madalad, Parth, Soosh, _Adam, adriro, csanuragjain, danyams, eyexploit, gasperpre, gz627, gzeon, hansfriese, hihen, immeas, jadezti, jonatascm, kiki_dev, kree-dotcom, ladboy233, lukris02, lumoswiz, mahdikarimi, minhtrng, nalus, nameruse, obront, reassor, rvierdiiev, seyni, tnevler, wait, yixxas
1.3417 USDC - $1.34
https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L81
In some situations (e.g. the sales amount never reaches the amount for auction), LPDA
auction sale may never end. When this happens, the seller/artist cannot get the sales imcome and the feeReceiver
cannot receive the fee.
feeReceiver
cannot receive the fee.Manual audit.
Some measures can be implemented to solve the problem. For example:
The sale.saleReceiver
can withdraw the current balance (minus fees), and at the same time transer the fees to feeReceiver
; or alternatively, the feeReceiver
can withdraw its current fees and at the same time transfer the remaining balance to sale.saleReceiver
.
or:
The seller/artist have the ability to end the auction after some pre-specified time.
#0 - c4-judge
2022-12-12T09:02:25Z
berndartmueller marked the issue as duplicate of #328
#1 - c4-judge
2023-01-02T20:22:50Z
berndartmueller marked the issue as satisfactory