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: 14/116
Findings: 3
Award: $1,011.89
π Selected for report: 0
π Solo Findings: 0
π Selected for report: plasmablocks
Also found by: 0xabhay, BARW, hals, serial-coder
227.371 USDC - $227.37
https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Create.sol#L733-L776 https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Stop.sol#L265-L285
Since the creation of a rental consumes less gas per rental NFT than stopping a rental, creating a rental with a lot of NFTs might be possible but stopping it might run out of gas. Not being able to stop a rental will lock the rented NFTs in the borrowers safe and prevent the lender to ever being paid.
To create a new rental the function Create:validateOrder()
is called, to stop a rental Stop::stopRental()
is called. Tests show that when creating a rental, every additional NFT added to the rental increases the gas cost of the transaction by at least 29,000 gas. When stopping a rental, every additional NFT adds at least 36,200 gas to the transaction.
For convenience here is a table of the gas cost per additional NFT for different numbers of NFTs in a rental:
Number of NFTs | Gas "validateOrder" | Gas per NFT | Gas "stopRental" | Gas per NFT |
---|---|---|---|---|
1 | 156,218 | 97,523 | ||
2 | 185,208 | 28,990 | 113,731 | 36,208 |
5 | 272,225 | 29,006 | 242,394 | 36,221 |
10 | 417,398 | 29,035 | 423,613 | 36,244 |
100 | 3,061,615 | 29,380 | 3,709,491 | 36,510 |
401 | 12,333,058 | 30,802 | 15,028,311 | 37,604 |
As can be seen in the table above, creating a rental of 401 NFTs would be possible but stopping the rental would cost over 15 million gas. This is more than the gas block limit of Avalanche, one of the chains the reNFT protocol will be deployed on and will lead to an OOG revert. Therfore the rental can never be stopped and the rented NFTs will be stuck in the borrowers safe and the lender will never get paid.
Even though the number of 401 NFTs is quite high, it is not impossible to reach, depending on the game and the NFTs that are rented (e.g cosmetics for land plots in an βAxie Infinity Landβ kind of game). Also, this calculation is not considering any additional gas used for onStop
hooks or multiple ERC20 tokens for payment. Such additional gas cost could dramatically reduce the number of NFTs needed for an OOG event.
Following is a test that can be added to StopRent.t.sol
to check the gas cost for calling Create: validateOrder()
and Stop::stopRental
. To be able to run the test the number of tokens deployed must be changed in test/fixtures/protocol/AccountCreator.sol::setUp
from 3 to 500:
// deploy 3 erc20 tokens, 3 erc721 tokens, and 3 erc1155 tokens - _deployTokens(3); + _deployTokens(1000);
In the test itself, the number of NFTs in the rental can be changed by adjusting the numberOfNfts
variable in the beginning of the test. To run the test run:
forge test -vvv --gas-report --mt test_OOG_StopOrder
In the gas report look for the gas cost for the call to Create:validateOrder()
and Stop::stopRental
.
function test_OOG_StopOrder() public { uint256 numberOfNfts = 100; createOrder({ offerer: alice, orderType: OrderType.BASE, erc721Offers: numberOfNfts, erc1155Offers: 0, erc20Offers: 0, erc721Considerations: 0, erc1155Considerations: 0, erc20Considerations: 1 }); ( Order memory order, bytes32 orderHash, OrderMetadata memory metadata ) = finalizeOrder(); createOrderFulfillment({ _fulfiller: bob, order: order, orderHash: orderHash, metadata: metadata }); // finalize the base order fulfillment uint256 gasBefore4 = gasleft(); RentalOrder memory rentalOrder = finalizeBaseOrderFulfillment(); uint256 gasAfter4 = gasleft(); console.log("Gas for creating rental", gasBefore4 - gasAfter4); // speed up in time past the rental expiration vm.warp(block.timestamp + 750); // stop the rental order vm.prank(alice.addr); uint256 gasBefore5 = gasleft(); stop.stopRent(rentalOrder); }
Manual review Foundry
To ensure all rentals can be stopped consider introduceing a cap for the number of NFTs that can be added to a single rental. If a user want to rent out more NFTs he can still create multiple rentals
DoS
#0 - c4-pre-sort
2024-01-21T17:47:36Z
141345 marked the issue as duplicate of #544
#1 - c4-judge
2024-01-27T18:25:36Z
0xean marked the issue as satisfactory
#2 - c4-judge
2024-01-29T10:32:14Z
0xean changed the severity to 2 (Med Risk)
π Selected for report: AkshaySrivastav
Also found by: 0xA5DF, 0xAlix2, 0xDING99YA, 0xdice91, BARW, BI_security, EV_om, J4X, Jorgect, SBSecurity, ZdravkoHr, evmboi32, hals, haxatron, imare, juancito, kaden, marqymarq10, oakcobalt, rbserver, rokinot, rvierdiiev, said, serial-coder, trachev
4.7844 USDC - $4.78
A lender can not add a hook to his rental that should only be executed when the rental is stopped, because creating the rental will fail.
When a new rental is created, Create::validateOrder
is called with the ZoneParameters
. This ZoneParameters
include a RentalPayload
which again includes the OrderMetadata
. The order meta data includes, among other things, an array of Hooks
that should be added to the rental order. This hook are executed when the rental order is created (onStart
) or when the rental order is stopped (onStop
). The Storage
holds a whitelist of all hooks that can be added to a rental order. Hooks can be whitelisted to be onStart
or onStop
or both.
The problem arises in the function Create::_addHooks
that is called during the creation of a rental. This function loops over all provided hooks and checks if they are whitelisted as onStart
. If not, the function reverts:
// Check that the hook is reNFT-approved to execute on rental start. if (!STORE.hookOnStart(target)) { revert Errors.Shared_DisabledHook(target); }
According to the team a hook that is only supposed to be executed onStop
, will not be whitelisted as onStart
. This means that if a lender would like to add such a only onStop
hook to his rental, the creation of the rental will revert.
Manual review
During the creation of a rental, when checking the hooks a lender wants to add, consider not reverting on hooks that are not whitelisted as onStart
but also checking if the hook is whitelisted as onStop
. If so, skip the execution part of the loop and continue with the next hook. If the loop is neither whitelisted as onStart
nor onStop
, revert the function.
Other
#0 - c4-pre-sort
2024-01-21T17:59:15Z
141345 marked the issue as duplicate of #501
#1 - c4-judge
2024-01-28T19:36:42Z
0xean marked the issue as satisfactory
#2 - BenRai1
2024-01-31T09:35:59Z
Hi @0xean,
this issue is not a dublicat of https://github.com/code-423n4/2024-01-renft-findings/issues/501 https://github.com/code-423n4/2024-01-renft-findings/issues/501 is about a onStop hook been deactivated during the rental time, this issue is that onStop hooks can prevent users from creating a rental in the first place. Please have a second look
π Selected for report: AkshaySrivastav
Also found by: 0xA5DF, 0xAlix2, 0xDING99YA, 0xdice91, BARW, BI_security, EV_om, J4X, Jorgect, SBSecurity, ZdravkoHr, evmboi32, hals, haxatron, imare, juancito, kaden, marqymarq10, oakcobalt, rbserver, rokinot, rvierdiiev, said, serial-coder, trachev
4.7844 USDC - $4.78
If a lender has added a hook to his rental that should only be executed onStart
, it will not be possible to stop the rental because the call to Stop::stopRent
will always fail. This will result in the rented NFTs beeing stuck in the borrowers safe and the lender never receiving his payment for the rent.
When a rental is stopped, Stop::stopRent
is called with the RentalOrder
data of the rental that should be stopped.
Part of the RentalOrder
data is an array of hooks that were added to the rental. This hook are executed when the rental order is created (onStart
) or when the rental order is stopped (onStop
). The Storage
holds a whitelist of all hooks that can be added to a rental order. Hooks can be whitelisted to be only onStart
or only onStop
or on onStart
and onStop
.
The problem arises in the function Stop::_removeHooks
that is called during the process of stopping a rental. This function loops over all provided hooks and checks if they are whitelisted as onStop
. If not, the function reverts:
// Check that the hook is reNFT-approved to execute on rental stop. if (!STORE.hookOnStop(target)) { revert Errors.Shared_DisabledHook(target); }
According to the team, a hook that is only supposed to be executed onStart
, will not be whitelisted as onStop
. This means that if a lender added a hook to his rental that is only onStart
, stopping the rental will not be possible since the call to Stop::_removeHooks
will always revert because of the check shown above. This will leave the rented items stuck in the safe of the borrower and prevent the lender being payed for the rental.
Manual review
During the process of stopping a rental, when checking the hooks that have been added to the rental, consider not reverting on hooks that are not whitelisted as onStop
but also check if they are whitelisted as onStart
. If a hook is not whitelisted as onStop
but is whitelisted as onStart
, skip the execution part of the loop and continue with checking the next hook. If the loop is neither whitelisted as onStart
nor onStop
, revert the function.
Invalid Validation
#0 - c4-pre-sort
2024-01-21T17:59:13Z
141345 marked the issue as duplicate of #501
#1 - c4-judge
2024-01-28T19:36:37Z
0xean marked the issue as satisfactory
#2 - c4-judge
2024-01-28T20:47:34Z
0xean changed the severity to 2 (Med Risk)
#3 - BenRai1
2024-01-31T09:37:50Z
Hi @0xean,
this issue is not a dublicat of https://github.com/code-423n4/2024-01-renft-findings/issues/501 https://github.com/code-423n4/2024-01-renft-findings/issues/501 is about a onStop hook been deactivated during the rental time, this issue is that an onStart hook that is only onStart can prevent the closing of a rental without conciddering any onStop hooks. Please have a second look
π Selected for report: AkshaySrivastav
Also found by: 0xA5DF, 0xAlix2, 0xDING99YA, 0xdice91, BARW, BI_security, EV_om, J4X, Jorgect, SBSecurity, ZdravkoHr, evmboi32, hals, haxatron, imare, juancito, kaden, marqymarq10, oakcobalt, rbserver, rokinot, rvierdiiev, said, serial-coder, trachev
4.7844 USDC - $4.78
If a lender adds a hook that should be executed onStop
, but this hook is removed from the whitelist during the rental time, it will not be possible to stop the rental since the call to Stop::stopRent
will fail. This will result in the rented NFTs beeing stuck in the borrowers safe and the lender never receiving his payment for the rent.
When a rental is stopped, Stop::stopRent
is called with the RentalOrder
data of the rental.
Part of the RentalOrder
data is an array of hooks that were added to the rental when it was created. This hook can be executed when the rental order is created (onStart
) or when the rental order is stopped (onStop
). The Storage
holds a whitelist of all hooks that should be executed onStart
and onStop
. Hooks that were added to the whitelist can also be removed from it.
The problem arises in the function Stop::_removeHooks
that is called during the process of stopping a rental. This function loops over all provided hooks and checks if they are whitelisted as onStop
. If not, the function reverts:
// Check that the hook is reNFT-approved to execute on rental stop. if (!STORE.hookOnStop(target)) { revert Errors.Shared_DisabledHook(target); }
If a hook was whitelisted as onStop
when the rental was created but was removed from the whitelist during the rental time, this will prevent the rental to be stopped since the function will always revert. The result would be the rented NFTs beeing stuck in the safe of the borrower and the lender never getting his payment.
Manual review
During the process of stopping a rental, when checking the hooks that have been added to the rental, consider not reverting on hooks that are not whitelisted as onStop
since they might have been removed from the whitelist during the rental period. Instead just skip the execution part of the loop and continue with checking the next hook.
Timing
#0 - c4-pre-sort
2024-01-21T19:19:02Z
141345 marked the issue as duplicate of #238
#1 - c4-pre-sort
2024-01-28T05:44:22Z
141345 marked the issue as not a duplicate
#2 - c4-pre-sort
2024-01-28T05:46:04Z
141345 marked the issue as duplicate of #501
#3 - c4-judge
2024-01-28T19:36:31Z
0xean marked the issue as satisfactory
#4 - c4-judge
2024-01-28T20:47:35Z
0xean changed the severity to 2 (Med Risk)
π Selected for report: AkshaySrivastav
Also found by: 0xA5DF, 0xAlix2, 0xDING99YA, 0xdice91, BARW, BI_security, EV_om, J4X, Jorgect, SBSecurity, ZdravkoHr, evmboi32, hals, haxatron, imare, juancito, kaden, marqymarq10, oakcobalt, rbserver, rokinot, rvierdiiev, said, serial-coder, trachev
4.7844 USDC - $4.78
If a lender adds a hook that should be executed onStop
, but the execution of this hook fails for any reason that cannot be fixed, it will not be possible to ever stop the rental since the call to Stop::stopRent
will fail. This will result in the rented NFTs being stuck in the borrowers safe and the lender never receiving his payment for the rent.
When a rental is stopped, Stop::stopRent
is called with the RentalOrder
data of the rental.
Part of the RentalOrder
data is an array of hooks that were added to the rental when it was created. This hook can be executed when the rental order is created (onStart
) or when the rental order is stopped (onStop
). All hooks that are onStop
need to be executed when stopping a rental.
If the execution of a hook fails, the function reverts. If the reason why the hook failed can not be fixed, there is no way to skip the execution of this hook. In such a case the rental can never be stopped, the rented NFTs will be stuck in the safe of the borrower and the lender will never get paid.
Manual review
Consider adding a βblacklistβ of hooks to the Storage that holds hook that should be skipped when stopping a rental. Check this blacklist before executing any onStop
hook. This would allow the admin to add failing hook that cannot be fixed to the blacklist and enable lenders to stop their rent even if they had added such a failing hooks when creating the rental.
DoS
#0 - c4-pre-sort
2024-01-21T17:43:09Z
141345 marked the issue as duplicate of #285
#1 - c4-judge
2024-01-27T17:11:59Z
0xean marked the issue as duplicate of #170
#2 - c4-judge
2024-01-27T17:14:10Z
0xean marked the issue as not a duplicate
#3 - c4-judge
2024-01-27T17:14:36Z
0xean changed the severity to QA (Quality Assurance)
#4 - c4-judge
2024-01-28T21:44:03Z
This previously downgraded issue has been upgraded by 0xean
#5 - c4-judge
2024-01-28T21:44:19Z
0xean marked the issue as duplicate of #501
#6 - c4-judge
2024-01-28T21:44:26Z
0xean marked the issue as satisfactory
#7 - c4-judge
2024-01-28T21:44:34Z
0xean marked the issue as partial-50
#8 - c4-judge
2024-01-28T21:44:41Z
0xean marked the issue as partial-25
π Selected for report: AkshaySrivastav
Also found by: 0xA5DF, 0xAlix2, 0xDING99YA, 0xdice91, BARW, BI_security, EV_om, J4X, Jorgect, SBSecurity, ZdravkoHr, evmboi32, hals, haxatron, imare, juancito, kaden, marqymarq10, oakcobalt, rbserver, rokinot, rvierdiiev, said, serial-coder, trachev
4.7844 USDC - $4.78
All gnosis safes launched by the Factory are given the Stop policy
as a module and the Guard policy as a guard. If the Stop policy
is ever deactivated, all NFTs will be stuck in the safes where the Stop policy
is set as a module since it will not be possible to call stopRent anymore.
For a user to be able to rent NFTs through the reNFT protocol, he first needs to create a gonsis safe by calling the function deployRentalSafe
in the factory. By doing this, a new safe is deployed and the Stop policy
is set as a module for the safe and the Guard
policy is set as the guard for the safe. This is done to ensure that the rented NFTs are not transferred out of the safe during the rental phase.
For this purpose, all transactions from the safe are checked by the guard to ensure that they do not contain forbidden actions like approving or transferring NFTs, changing the guard or activating or deactivating new modules.
The Stop policy
on the other hand has the appropriate roles to be able call Escrow
and State
and thereby to stop a rental and move the ERC20 tokens and NFTs associated with the rental around.
The problem arises if the Stop policy
is ever deactivated by the executor through the Kernal. If this is the case, the Stop policy
will loose all its rights to interact with the Escrow
and State
contracts and it will not be possible anymore to stop a rental, move the NFTs back to the lender and pay the lender. Mathers are made worse by the fact that no new Stop policy
can be activated for the gnosis safe since this would be prevented by the guard. If the Stop policy
is ever deactivated, all gnosis saves that were deployed by the factory will be affected.
Manual review
To prevent the issue described above consider creating a whitelist of policies that can never be deactivated in the kernal. Check this whitelist when _deactivatePolicy
is called to ensure to not deactivate a crucial policy.
Access Control
#0 - c4-pre-sort
2024-01-21T17:58:38Z
141345 marked the issue as duplicate of #501
#1 - c4-pre-sort
2024-01-28T07:04:03Z
141345 marked the issue as not a duplicate
#2 - c4-pre-sort
2024-01-28T07:04:11Z
141345 marked the issue as duplicate of #238
#3 - c4-judge
2024-01-28T20:27:38Z
0xean marked the issue as not a duplicate
#4 - c4-judge
2024-01-28T20:27:50Z
0xean marked the issue as duplicate of #501
#5 - c4-judge
2024-01-28T22:46:03Z
0xean marked the issue as satisfactory
#6 - BenRai1
2024-01-31T09:47:28Z
Hi @0xean,
this issue is not a dublicat of https://github.com/code-423n4/2024-01-renft-findings/issues/501 https://github.com/code-423n4/2024-01-renft-findings/issues/501 is about a onStop hook been deactivated during the rental time, this issue is about the stopPolicy that is added to all safes. Please have a second look
π Selected for report: CipherSleuths
Also found by: BARW
779.7358 USDC - $779.74
https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/modules/PaymentEscrow.sol#L190-L202 https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/policies/Stop.sol#L265-L306
In certain scenarios, if a borrower disables the ERC777 transfer with tokensReceived, the process can be reversed on tokensReceived. This action can consequently initiate a chain of reverts.
PaymentEscrow._settlePaymentInFull
revertPaymentEscrow._settlePayment
revertPaymentEscrow.settlePayment
revertStop.stopRent
revert
Consequently, the borrower's PAY
rentals can't be halted, allowing the borrower to retain the NFT indefinitely.tokensReceived
tokensReceived
Foundry
Let user claim their fee instead of send it to user address.
ERC20
#0 - c4-pre-sort
2024-01-21T18:21:08Z
141345 marked the issue as insufficient quality report
#1 - c4-judge
2024-01-26T18:28:18Z
0xean marked the issue as unsatisfactory: Insufficient quality
#2 - BenRai1
2024-01-31T09:51:20Z
Hi @0xean, I am wondering why this issue is marked as insuficiant. I know that it is a little bit short but it still shows a valid issue in my opinion. Would be great to hear the reasening so we can learn for the future.
#3 - c4-judge
2024-02-01T10:56:12Z
0xean removed the grade
#4 - c4-judge
2024-02-01T10:56:28Z
0xean marked the issue as duplicate of #462
#5 - c4-judge
2024-02-01T10:56:32Z
0xean marked the issue as partial-25
#6 - c4-judge
2024-02-01T11:15:07Z
0xean marked the issue as not a duplicate
#7 - c4-judge
2024-02-01T11:17:46Z
0xean marked the issue as duplicate of #487
#8 - c4-judge
2024-02-01T15:50:00Z
0xean marked the issue as satisfactory