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: 104/119
Findings: 1
Award: $0.84
🌟 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
A Dutch Auction is a type of auction in which the auctioneer begins with a high asking price in the case of selling, and lowers it until some participant accepts the price, or it reaches a predetermined reserve price. In this project, an edition
is sold through this mechanism by setting the parameters which will rule the auction.
A problem arises when the defined dropPerSecond
results in a bigger value than the starting price during or after the auction when calculating the price drop, which results in Arithmetic over/underflow
in getPrice(). This in turn prevents the refund
and buy
functions.
External requirements:
Confirm the behaviour by altering test_LPDA() to:
function test_LPDA() public { // make the lpda sales contract // @audit overwriting original DPS to 1.1, bigger than the starting price lpdaSale.dropPerSecond = uint80(uint256(1.1 ether) / 1 days); sale = LPDA(lpdaSales.createLPDASale(lpdaSale)); // authorize the lpda sale to mint tokens edition.grantRole(edition.MINTER_ROLE(), address(sale)); //lets buy an NFT sale.buy{value: 1 ether}(1); assertEq(address(sale).balance, 1 ether); vm.warp(block.timestamp + 1 days); // @audit getPrice() the culprit, but will also fail in the next call assertApproxEqRel(sale.getPrice(), 0.9 ether, lpdaSale.dropPerSecond); // buy the rest // this will auto end the sale sale.buy{value: uint256((0.9 ether + lpdaSale.dropPerSecond) * 9)}(9); vm.warp(block.timestamp + 2 days); // now lets get a refund uint256 bal = address(this).balance; sale.refund(); //assertApproxEqRel(address(this).balance, bal + 0.1 ether, lpdaSale.dropPerSecond); }
Foundry
In order to prevent underflow, when creating an LPDA.Sale
check if it's possible with the last requirement:
function createLPDASale(LPDA.Sale calldata sale) external returns (address clone) { require(IEscher721(sale.edition).hasRole(bytes32(0x00), msg.sender), "NOT AUTHORIZED"); require(sale.saleReceiver != address(0), "INVALID SALE RECEIVER"); require(sale.startTime >= block.timestamp, "INVALID START TIME"); require(sale.endTime > sale.startTime, "INVALID END TIME"); require(sale.finalId > sale.currentId, "INVALID FINAL ID"); require(sale.startPrice > 0, "INVALID START PRICE"); require(sale.dropPerSecond > 0, "INVALID DROP PER SECOND"); uint96 fullTimeElapsed = sale.endTime - sale.startTime; require(sale.dropPerSecond * fullTimeElapsed < sale.startPrice, "DPS MAY UNDERFLOW"); clone = implementation.clone(); LPDA(clone).initialize(sale); emit NewLPDAContract(msg.sender, sale.edition, clone, sale); }
#0 - c4-judge
2022-12-11T11:37:14Z
berndartmueller marked the issue as duplicate of #392
#1 - c4-judge
2023-01-02T19:54:20Z
berndartmueller marked the issue as satisfactory
#2 - c4-judge
2023-01-02T19:54:27Z
berndartmueller changed the severity to 3 (High Risk)