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: 2/72
Findings: 2
Award: $10,871.41
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 Selected for report: KingNFT
10835.0653 USDC - $10,835.07
The '_executeNonAtomicOrders' function in SeaportProxy.sol tries to send fee by batch, this may break the 'NonAtomic' feature.
Let's say user want to buy 3 NFTs with following order parameters
NFT_1: price = 100 USDT, fee = 5% NFT_2: price = 200 USDT, fee = 5% NFT_3: price = 300 USDT, fee = 5%
Given user only sends 600 USDT, the expected result should be
NFT_1: success NFT_2: success NFT_3: failed
But as the fees are batched and sent at last, cause all 3 orders failed.
function _executeNonAtomicOrders( BasicOrder[] calldata orders, bytes[] calldata ordersExtraData, address recipient, uint256 feeBp, address feeRecipient ) private { uint256 fee; address lastOrderCurrency; for (uint256 i; i < orders.length; ) { OrderExtraData memory orderExtraData = abi.decode(ordersExtraData[i], (OrderExtraData)); AdvancedOrder memory advancedOrder; advancedOrder.parameters = _populateParameters(orders[i], orderExtraData); advancedOrder.numerator = orderExtraData.numerator; advancedOrder.denominator = orderExtraData.denominator; advancedOrder.signature = orders[i].signature; address currency = orders[i].currency; uint256 price = orders[i].price; 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 {} unchecked { ++i; } } if (fee > 0) _transferFee(fee, lastOrderCurrency, feeRecipient); }
VS Code
Don't batch up fees.
#0 - 0xhiroshi
2022-11-23T14:10:05Z
I would treat this as a malicious user trying to buy NFTs without paying for fees as he should be paying 600 x 1.05 = 630 instead of 600. So I think it's ok to just let it revert.
@0xJurassicPunk thoughts?
#1 - 0xhiroshi
2022-11-23T14:10:18Z
I would treat this as a malicious user trying to buy NFTs without paying for fees as he should be paying 600 x 1.05 = 630 instead of 600. So I think it's ok to just let it revert.
@0xJurassicPunk thoughts?
#2 - c4-sponsor
2022-11-23T14:10:38Z
0xhiroshi marked the issue as sponsor disputed
#3 - c4-sponsor
2022-11-23T14:10:48Z
0xhiroshi marked the issue as sponsor acknowledged
#4 - c4-judge
2022-12-11T12:48:12Z
Picodes marked the issue as satisfactory
#5 - ydspa
2022-12-19T01:40:04Z
Sorry for not indicated clearly, the reason why i think this may happen is that the NFT price may increase during submitting of tx, not due to a malicious user.
#6 - 0xhiroshi
2022-12-19T08:51:29Z
Sorry for not indicated clearly, the reason why i think this may happen is that the NFT price may increase during submitting of tx, not due to a malicious user.
Even if the seller submits a new listing with a higher price, the current transaction should still be valid. When there are two listings for the same NFT, they are both valid unless the seller explicitly cancels the original order on-chain, usually through the cancellation of an order nonce.
🌟 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
[L-01] Miss check for 'recipient' in 'setFee()' function
function setFee( address proxy, uint256 bp, address recipient // @audit miss check if it's address(0) ) external onlyOwner { if (bp > 10000) revert FeeTooHigh(); _proxyFeeData[proxy].bp = bp; _proxyFeeData[proxy].recipient = recipient; emit FeeUpdated(proxy, bp, recipient); }
[L-02] Miss check for 'to' in 'rescueERC721()' function
function rescueERC721( address collection, address to, // @audit miss check 'to' uint256 tokenId ) external onlyOwner { _executeERC721TransferFrom(collection, address(this), to, tokenId); }
[L-03] Miss check for 'to' in 'rescueERC1155()' function
function rescueERC1155( address collection, address to, // @audit miss check 'to' uint256[] calldata tokenIds, uint256[] calldata amounts ) external onlyOwner { _executeERC1155SafeBatchTransferFrom(collection, address(this), to, tokenIds, amounts); }
[L-04] It's recommended to use 'safeTransferFrom' interface to transfer ERC721 token
function _executeERC721TransferFrom( address collection, address from, address to, uint256 tokenId ) internal { (bool status, ) = collection.call(abi.encodeWithSelector(IERC721.transferFrom.selector, from, to, tokenId)); // @audit not safe if (!status) revert ERC721TransferFromFail(); }
#0 - c4-judge
2022-11-21T19:45:42Z
Picodes marked the issue as grade-b
#1 - 0xhiroshi
2022-11-23T14:46:52Z
L-01: Invalid L-02: Invalid L-03: Invalid L-04: Valid
#2 - c4-sponsor
2022-11-23T14:47:00Z
0xhiroshi requested judge review
#3 - 0xhiroshi
2022-12-12T23:35:34Z
@Picodes what is the edict of this report?