Platform: Code4rena
Start Date: 08/11/2022
Pot Size: $60,500 USDC
Total HM: 6
Participants: 72
Period: 5 days
Judge: Picodes
Total Solo HM: 2
Id: 178
League: ETH
Rank: 28/72
Findings: 2
Award: $187.67
🌟 Selected for report: 0
🚀 Solo Findings: 0
151.3257 USDC - $151.33
https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/lowLevelCallers/LowLevelETH.sol#L35 https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/lowLevelCallers/LowLevelETH.sol#L46 https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/lowLevelCallers/LowLevelETH.sol#L57 https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/prototype/V0Aggregator.sol#L61 https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/LooksRareAggregator.sol#L109
When users uses ETH to batch buy NFTs, if the transaction fails, the aggregator contract will call the function _returnETHIfAny to return the ETH to the user. This function transfers all the balance in the contract to the recipient through call, but there is no check of return value to the call. Therefore, there may be two problems: one is that there is a possibility that the user fails to withdraw ETH when transactions fail, and the other is that if the first user fails to withdraw ETH, then the next user who needs to withdraw ETH can get more ETH.
/** * @notice Return ETH back to the original sender if any ETH is left in the payable call. */ function _returnETHIfAny() internal { assembly { if gt(selfbalance(), 0) { let status := call(gas(), caller(), selfbalance(), 0, 0, 0, 0) } } } /** * @notice Return ETH back to the designated sender if any ETH is left in the payable call. */ function _returnETHIfAny(address recipient) internal { assembly { if gt(selfbalance(), 0) { let status := call(gas(), recipient, selfbalance(), 0, 0, 0, 0) } } } /** * @notice Return ETH to the original sender if any is left in the payable call but leave 1 wei of ETH in the contract. */ function _returnETHIfAnyWithOneWeiLeft() internal { assembly { if gt(selfbalance(), 1) { let status := call(gas(), caller(), sub(selfbalance(), 1), 0, 0, 0, 0) } } }
The return value of ETH transfer is not checked, therefore, if this call fails, user may fail to withdraw ETH, or if the first user fails to withdraw ETH, then the next user who needs to withdraw ETH can get more ETH.
It is recommended to check the return value, and when the return value is false, just revert.
#0 - c4-judge
2022-11-21T11:11:50Z
Picodes marked the issue as duplicate of #241
#1 - c4-judge
2022-12-16T13:58:49Z
Picodes marked the issue as satisfactory
🌟 Selected for report: RaymondFam
Also found by: 0x1f8b, 0x52, 0xSmartContract, 0xc0ffEE, 0xhacksmithh, 8olidity, Awesome, BClabs, Bnke0x0, Chom, Deivitto, Hashlock, IllIllI, Josiah, KingNFT, Nyx, R2, ReyAdmirado, Rolezn, SamGMK, Sathish9098, SinceJuly, V_B, Vadis, Waze, a12jmx, adriro, ajtra, aphak5010, bearonbike, bin, brgltd, carlitox477, carrotsmuggler, cccz, ch0bu, chaduke, datapunk, delfin454000, erictee, fatherOfBlocks, fs0c, horsefacts, jayphbee, ktg, ladboy233, pashov, perseverancesuccess, rbserver, ret2basic, tnevler, zaskoh
36.3434 USDC - $36.34
The fulfillAdvancedOrder may return false on an order not successfully fulfilled. The try/catch only skip current order when it reverts on fail, while still charge fee on user when it returns false.
try marketplace.fulfillAdvancedOrder{value: currency == address(0) ? price : 0}( advancedOrder, new CriteriaResolver[](0), bytes32(0), recipient ) { if (feeRecipient != address(0)) { uint256 orderFee = (price * feeBp) / 10000; if (currency == lastOrderCurrency) { fee += orderFee; } else { if (fee > 0) _transferFee(fee, lastOrderCurrency, feeRecipient); lastOrderCurrency = currency; fee = orderFee; } } } catch {}
when marketplace.fulfillAdvancedOrder return false (but not reverted), the code in the try clause will execute, and charge the fee on the user. However, it is inappropriate to charge a fee on a not fulfilled order.
also check return value on marketplace.fulfillAdvancedOrder
try marketplace.fulfillAdvancedOrder{value: currency == address(0) ? price : 0}( advancedOrder, new CriteriaResolver[](0), bytes32(0), recipient ) returns (bool fulfilled) { if (fulfilled && feeRecipient != address(0)) { uint256 orderFee = (price * feeBp) / 10000; if (currency == lastOrderCurrency) { fee += orderFee; } else { if (fee > 0) _transferFee(fee, lastOrderCurrency, feeRecipient); lastOrderCurrency = currency; fee = orderFee; } } } catch {}
#0 - c4-judge
2022-11-21T16:45:05Z
Picodes marked the issue as duplicate of #93
#1 - c4-judge
2022-12-11T13:36:14Z
Picodes marked the issue as not a duplicate
#2 - c4-judge
2022-12-11T13:36:36Z
Picodes changed the severity to QA (Quality Assurance)
#3 - c4-judge
2022-12-11T13:36:41Z
Picodes marked the issue as grade-b