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: 5/72
Findings: 3
Award: $3,938.27
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: ronnyx2017
Also found by: 0x52
3750.5995 USDC - $3,750.60
When calling SeaportProxy#execute the user is allowed to supply a price field for each order. This price is used to determine the amount to pay fees on. For orders fulfilled by ERC20 tokens a user can set price = 0 and skip all fees.
User can avoid paying all fees on ERC20 orders
for (uint256 i; i < ordersLength; ) { OrderExtraData memory orderExtraData = abi.decode(ordersExtraData[i], (OrderExtraData)); advancedOrders[i].parameters = _populateParameters(orders[i], orderExtraData); advancedOrders[i].numerator = orderExtraData.numerator; advancedOrders[i].denominator = orderExtraData.denominator; advancedOrders[i].signature = orders[i].signature; if (orders[i].currency == address(0)) ethValue += orders[i].price; unchecked { ++i; } } (bool[] memory availableOrders, ) = marketplace.fulfillAvailableAdvancedOrders{value: ethValue}( advancedOrders, new CriteriaResolver[](0), extraDataStruct.offerFulfillments, extraDataStruct.considerationFulfillments, bytes32(0), recipient, ordersLength );
SeaportProxy#execute allows the user to supply an array of orders. order.price is a user supplied value that is used to determine the amout of fees. For orders filled with native ETH it determines the msg.value to send to the OpenSea marketplace, but for ERC20 orders their sole purpose it for fee calculation. The result is that a user can specify an order.price of 0 for all ERC20 orders and avoid paying any fees on those orders.
Manual Review
In SeaportProxy#_populateParameters the first consideration amount should be set to order.price
rather than orderExtraData.recipients[j].amount
:
for (uint256 j; j < recipientsLength; ) { // We don't need to assign value to identifierOrCriteria as it is always 0. + if (j > 0) { + uint256 recipientAmount = orderExtraData.recipients[j].amount; + } else { + uint256 recipientAmount = order.price; + } - uint256 recipientAmount = orderExtraData.recipients[j].amount; consideration[j].startAmount = recipientAmount; consideration[j].endAmount = recipientAmount; consideration[j].recipient = payable(orderExtraData.recipients[j].recipient); consideration[j].itemType = order.currency == address(0) ? ItemType.NATIVE : ItemType.ERC20; consideration[j].token = order.currency; unchecked { ++j; } }
#0 - c4-judge
2022-11-21T12:03:49Z
Picodes marked the issue as duplicate of #143
#1 - c4-judge
2022-12-16T13:55:04Z
Picodes marked the issue as satisfactory
151.3257 USDC - $151.33
https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/lowLevelCallers/LowLevelETH.sol#L32-L38 https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/lowLevelCallers/LowLevelETH.sol#L43-L49 https://github.com/code-423n4/2022-11-looksrare/blob/e3b2c053f722b0ca2dce3a3eb06f64859b8b7a6f/contracts/lowLevelCallers/LowLevelETH.sol#L54-L61
ETH transfer my fail resulting in ETH not being returned to user
function _returnETHIfAny() internal { assembly { if gt(selfbalance(), 0) { let status := call(gas(), caller(), selfbalance(), 0, 0, 0, 0) } } }
In the low level functions that return ETH to the user after an aggregate trade fails to validate the return value of the ETH transfer. If the transfer fails, the user's ETH would become stuck in the contract.
Manual Review
Confirm that the call returns true for LowLevelETH#_returnETHIfAny, and _returnETHIfAnyWithOneWeiLeft:
function _returnETHIfAny() internal { assembly { if gt(selfbalance(), 0) { let status := call(gas(), caller(), selfbalance(), 0, 0, 0, 0) } } + if (!status) revert ETHTransferFail(); }
#0 - c4-judge
2022-11-19T12:41:37Z
Picodes marked the issue as duplicate of #241
#1 - c4-judge
2022-12-16T13:55:42Z
Picodes changed the severity to 2 (Med Risk)
#2 - c4-judge
2022-12-16T13:55:42Z
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
SeaportProxy will be unable to fill orders in which there are multiple considerations with different tokens
for (uint256 j; j < recipientsLength; ) { // We don't need to assign value to identifierOrCriteria as it is always 0. uint256 recipientAmount = orderExtraData.recipients[j].amount; consideration[j].startAmount = recipientAmount; consideration[j].endAmount = recipientAmount; consideration[j].recipient = payable(orderExtraData.recipients[j].recipient); consideration[j].itemType = order.currency == address(0) ? ItemType.NATIVE : ItemType.ERC20; consideration[j].token = order.currency; unchecked { ++j; } }
When setting consideration[j].token, it is always set to order.currency. In the case in which one of the considerations tokens is not order.currency the constructed consideration will not match the consideration of the desired OpenSea order, causing the order hash. Since the order hash is different the provided signature will not be valid to fill the order. The result is that the orders in which there are multiple considerations with different tokens, will be impossible to fill.
Manual Review
The AdditionalRecipient struct should contain a field to specify the consideration token
#0 - 0xhiroshi
2022-11-24T12:13:35Z
Our plan is to only support simple listings to begin with and a majority of them have only 1 consideration token (ETH). If we want to support complex considerations we can have a SeaportProxyV2.
#1 - c4-sponsor
2022-11-24T12:14:00Z
0xhiroshi marked the issue as sponsor disputed
#2 - Picodes
2022-12-11T13:37:49Z
To me this is part of the design's current limitation, and not a bug, so downgrading to QA
#3 - c4-judge
2022-12-11T13:37:58Z
Picodes changed the severity to QA (Quality Assurance)
#4 - c4-judge
2022-12-16T13:55:27Z
Picodes marked the issue as grade-b