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: 60/119
Findings: 2
Award: $50.22
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: immeas
Also found by: 0x52, 0xDecorativePineapple, AkshaySrivastav, HollaDieWaldfee, aviggiano, bin2chen, cryptonue, evan, ey88, fs0c, gzeon, hansfriese, hihen, jayphbee, minhquanym, pauliax, reassor, saian, wait
49.6134 USDC - $49.61
https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L58-L89
The buy()
function on Last Price Dutch Auction Sale doesn't check if auction is ended, may lead to user loss asset if user call with amount
0 with msg.value > 0
Ideally if the max id (finalId
) is reached, then the auction will end, so no user can call buy()
. If we see the FixedPrice
and OpenEdition
there is a selfdestruct
when _end()
, so user can't call buy()
again.
But for LPDA
, the _end()
doesn't have this selfdestruct
, thus there is a potential call user calling the buy()
.
The buy()
function only have one check require(newId <= temp.finalId, "TOO MANY");
to implicitly says it's already fulfilled the max id.
There is a scenario where user call buy()
function with providing the amount
with 0. If this user fill the amount
with 0 and send msg.value
, the buy()
will success since the require(msg.value >= amount * price, "WRONG PRICE");
and require(newId <= temp.finalId, "TOO MANY");
will passed. At the end, the user will not get any amount
minted but their msg.value
can't be refund(), because it will be transferred to saleReceiver
and feeReceiver
.
Even though user is hardly input 0 as the amount
, but still this can be an issue if for example the Frontend (UI) wrongly input the amount
value to 0, or any misleading action. This user lost of asset (msg.value) usually categorized as high issue, but since this is need additional factor, it suitable with medium.
File: LPDA.sol 58: function buy(uint256 _amount) external payable { 59: uint48 amount = uint48(_amount); 60: Sale memory temp = sale; 61: IEscher721 nft = IEscher721(temp.edition); 62: require(block.timestamp >= temp.startTime, "TOO SOON"); 63: uint256 price = getPrice(); 64: require(msg.value >= amount * price, "WRONG PRICE"); 65: 66: amountSold += amount; 67: uint48 newId = amount + temp.currentId; 68: require(newId <= temp.finalId, "TOO MANY"); 69: 70: receipts[msg.sender].amount += amount; 71: receipts[msg.sender].balance += uint80(msg.value); 72: 73: for (uint256 x = temp.currentId + 1; x <= newId; x++) { 74: nft.mint(msg.sender, x); 75: } 76: 77: sale.currentId = newId; 78: 79: emit Buy(msg.sender, amount, msg.value, temp); 80: 81: if (newId == temp.finalId) { 82: sale.finalPrice = uint80(price); 83: uint256 totalSale = price * amountSold; 84: uint256 fee = totalSale / 20; 85: ISaleFactory(factory).feeReceiver().transfer(fee); 86: temp.saleReceiver.transfer(totalSale - fee); 87: _end(); 88: } 89: }
Manual analysis
Add new state variable to check if the auction is ended, and put the guard in buy()
function, example:
bool isAuctionEnded; function _end() internal { emit End(sale); isAuctionEnded = true; } function buy(uint256 _amount) external payable { require(!isAuctionEnded, "AUCTION ENDED"); ... }
#0 - c4-judge
2022-12-11T14:48:56Z
berndartmueller marked the issue as duplicate of #16
#1 - c4-judge
2022-12-11T14:50:07Z
berndartmueller changed the severity to 3 (High Risk)
#2 - c4-judge
2023-01-04T10:13:42Z
berndartmueller marked the issue as satisfactory
#3 - C4-Staff
2023-01-10T22:21:33Z
JeeberC4 marked the issue as duplicate of #441
🌟 Selected for report: RaymondFam
Also found by: 0xdeadbeef0x, 0xhacksmithh, AkshaySrivastav, Awesome, Bnke0x0, CRYP70, HollaDieWaldfee, JC, Parth, Rahoz, Tutturu, __141345__, ahmedov, ajtra, asgeir, aviggiano, bin2chen, btk, carrotsmuggler, cccz, chaduke, cryptonue, dic0de, fatherOfBlocks, fs0c, hansfriese, jonatascm, karanctf, ladboy233, lumoswiz, martin, obront, pashov, pauliax, rvierdiiev, shark, simon135, supernova, tourist, yellowBirdy, zapaz, zaskoh
0.6136 USDC - $0.61
https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L85-L86 https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/FixedPrice.sol#L109 https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/OpenEdition.sol#L92
call()
should be used instead of transfer()
on an address payable
The use of the deprecated transfer() function for an address will inevitably make the transaction fail when:
File: FixedPrice.sol 109: ISaleFactory(factory).feeReceiver().transfer(address(this).balance / 20); File: LPDA.sol 85: ISaleFactory(factory).feeReceiver().transfer(fee); 86: temp.saleReceiver.transfer(totalSale - fee); ... 105: payable(msg.sender).transfer(owed); File: OpenEdition.sol 92: ISaleFactory(factory).feeReceiver().transfer(address(this).balance / 20);
Manual Analysis
Recommend using call() instead of transfer()
#0 - c4-judge
2022-12-10T12:02:45Z
berndartmueller marked the issue as duplicate of #99
#1 - c4-judge
2023-01-03T12:49:19Z
berndartmueller marked the issue as satisfactory