Escher contest - Josiah's results

A decentralized curated marketplace for editioned artwork.

General Information

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

Escher

Findings Distribution

Researcher Performance

Rank: 9/119

Findings: 1

Award: $991.64

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: RaymondFam

Also found by: Josiah

Labels

bug
2 (Med Risk)
satisfactory
duplicate-280

Awards

991.6428 USDC - $991.64

External Links

Lines of code

https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L58-L89

Vulnerability details

Impact

The function logic of buy() in LPDA.sol can be exploited by shrewd buyers to achieve the lowest finalPrice possible.

Proof of Concept

LPDA.sol#L58-L89

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.

Tools Used

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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter