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: 56/116
Findings: 3
Award: $47.82
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
For OrderType.Pay, lenders will lend their NFTs to a renter as well as pay them to use their NFTs.
Lenders can withdraw their NFT anytime through Stop.stopRent(), and will pay the pro-rated amount to the renter.
function stopRent(RentalOrder calldata order) external {
For example, if the rent duration is 100 seconds and the amount is 100 USDC, then if the lender withdraws his NFT after 10 seconds, he has to pay the renter 10 dollars.
stopRent()
is the only function which allows the lender to reclaim their NFT. A renter can grief the lender by being in an ERC20 blocklist (eg USDC).
stopRent()
calls ESCRW.settlePayment(order)
, which then calls _settlePayment()
and then _settlePaymentProRata()
. At the end, the ERC20 tokens are transferred to the renter and the lender.
function _settlePaymentProRata( address token, uint256 amount, address lender, address renter, uint256 elapsedTime, uint256 totalTime ) internal { // Calculate the pro-rata payment for renter and lender. (uint256 renterAmount, uint256 lenderAmount) = _calculatePaymentProRata( amount, elapsedTime, totalTime ); // Send the lender portion of the payment. > _safeTransfer(token, lender, lenderAmount); // Send the renter portion of the payment. > _safeTransfer(token, renter, renterAmount); }
If the renter address is in a blocklist, then the safetransfer function will revert, which in turns revert the whole function execution.
The renter has nothing to lose when griefing because he gets both the NFT for rental and the ERC20 payment.
Lenders cannot get back their NFT if renter blocklist his token.
VSCode
Recommend having a try/catch function for the settling of payment in PaymentEscrow.sol. If for any reason, the payment to the renter does not work, continue the execution and keep the payment in the escrow contract, so that the renter can claim the payment in the PaymentEscrow in his own time.
This way, the lender can get his NFT back.
ERC20
#0 - c4-pre-sort
2024-01-21T17:36:20Z
141345 marked the issue as duplicate of #64
#1 - c4-judge
2024-01-28T21:01:10Z
0xean marked the issue as satisfactory
🌟 Selected for report: 0xA5DF
Also found by: 0xmystery, 7ashraf, AkshaySrivastav, CipherSleuths, NentoR, SBSecurity, Tendency, ZanyBonzy, ZdravkoHr, dd0x7e8, hals, haxatron, invitedtea, jasonxiale, juancito, kaden, krikolkk, ladboy233, oakcobalt, peanuts, petro_1912, pkqs90, plasmablocks, ravikiranweb3, rbserver, rokinot, souilos
12.582 USDC - $12.58
The admin can set a fee of 10000, which is 100% of the payment
/** * @notice Sets the numerator for the fee. The denominator will always be set at * 10,000. * * @param feeNumerator Numerator of the fee. */ function setFee(uint256 feeNumerator) external onlyByProxy permissioned { // Cannot accept a fee numerator greater than 10000. > if (feeNumerator > 10000) { revert Errors.PaymentEscrow_InvalidFeeNumerator(); } // Set the fee. fee = feeNumerator; }
There should be a max upper limit cap of setting fees, like feeNumerator > 1000
(10%) max fees.
When deploying a rental safe in Factory.sol, the owners and threshold is checked.
function deployRentalSafe( address[] calldata owners, uint256 threshold ) external returns (address safe) { // Require that the threshold is valid. if (threshold == 0 || threshold > owners.length) { revert Errors.FactoryPolicy_InvalidSafeThreshold(threshold, owners.length); }
This check is not needed as the safe contract itself, in ISafe.setup()
, the function checks the owner and threshold.
function setupOwners(address[] memory _owners, uint256 _threshold) internal { // Threshold can only be 0 at initialization. // Check ensures that setup function can only be called once. require(threshold == 0, "GS200"); // Validate that threshold is smaller than number of added owners. require(_threshold <= _owners.length, "GS201");
In SafeERC20, callOptionalReturn checks success, returndata length and token.code.length.
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false // and not revert is the subcall reverts. (bool success, bytes memory returndata) = address(token).call(data); > return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; }
In PaymentEscrow, the address(token).code.length
is not checked.
function _safeTransfer(address token, address to, uint256 value) internal { // Call transfer() on the token. (bool success, bytes memory data) = token.call( abi.encodeWithSelector(IERC20.transfer.selector, to, value) ); if (!success || (data.length != 0 && !abi.decode(data, (bool)))) { revert Errors.PaymentEscrowModule_PaymentTransferFailed(token, to, value); }
Check the address(token).code.length as well.
There can be many use cases for renting NFTs. For example, a renter can rent an NFT and use it to play a game. In web3 games, there might be instances where the game project will mint NFTs to the user if they have the NFTs required. Sometimes, they use safeMint, or even safeTransfers.
The Gnosis safe does not have onERC721Received implemented. Thus, they cannot receive any form of tokens when safeMint or safeTransfers are called.
Have an extended policy (Like Stop or Guard) to ensure that safeTransfers and safeMints work for rental assets.
#0 - 141345
2024-01-22T13:56:05Z
469 peanuts l r nc 2 0 1
L 1 d dup of https://github.com/code-423n4/2024-01-renft-findings/issues/304 L 2 n L 3 l L 4 l
#1 - c4-judge
2024-01-27T20:31:14Z
0xean marked the issue as grade-b
🌟 Selected for report: fouzantanveer
Also found by: 0xA5DF, 0xepley, SAQ, SBSecurity, albahaca, catellatech, clara, hals, hunter_w3b, kaveyjoe, ladboy233, pavankv, peanuts, tala7985, yongskiws
32.5158 USDC - $32.52
reNFT is a protocol that allows lenders to lend their NFT to renters for a price. reNFT uses external integrations like Safe and Seaport to make the protocol more dynamic and secure.
Instead of a simple renting model, reNFT utilizes seaport to create a dynamic renting model. Lenders can specify their duration of lend, their NFTs, and how much they are willing to get (range) through Seaport.
There are also two prominent types of rent, BASE and PAY. For BASE rentals, lenders will list their NFT for rent and get paid an ERC20 of their choice. For PAY rentals, lenders will list their NFTs and also pay the renter pro-rated amounts for every second that they rent the NFTs.
The creation of rentals, signature, and fulfillment of rent orders is done through Seaport. When an order is fulfilled, the NFTs will be sent to a Safe owned by the renter, and the ERC20 tokens will be sent to the payment Escrow contract. Note that the renter has to create a Safe in order to rent the NFT from reNFT.
Once the rental duration is over, the rented NFT will be sent back to the lender through the Safe contract and the ERC20 tokens will be disbursed accordingly.
For PAY order types, lenders can withdraw their rented NFT at any time, but they have to pay a pro-rated payment to the renter.
The protocol was hard to understand at first glance, and requires prerequisite knowledge about Safe and Seaport
Before anything, I went to read up about Safe and their Github
I tried to find which functions in Safe is being used in the protocol (eg createProxyWithNonce, setup) and figure out how the protocol is integrating Safe into its contracts.
Next, I read up on Seaport, mostly watching 0age videos on Youtube and some Web Articles
I tried to understand how an order is created, signed and fulfilled, and how Seaport is being integrated effectively. Also, I tried learning some keywords such as Items, Considerations, Zone.
With my newfound knowledge of the external integration, I tried to navigate the codebase again, but was lost at the design pattern.
I realised that I needed some Default framework knowledge.
After all the learnings, I get a rough idea of the protocol structure and mechanism. There wasn't much to audit since a lot of mechanisms hinges on the admin, and will therefore fall under centralization risk. With that in mind, I decided to focus on PaymentEscrow.sol and Stop.sol, the contracts that handles the transfer of ERC20 tokens and NFTs (and also the entry point for stopping rentals).
Finally, I looked at the test to better understand the flow of the protocol and get a better sensing of how token moves around.
removeRentals()
, so the orderHash cannot be deleted easily.PaymentEscrow and Storage contracts are modules. Modules are internal-facing contracts that store shared state across the protocol. They do not do anything by themselves, and interacts with policy in order to change the internal state. Modules can only be accessed through whitelisted Policy contracts, and have no dependencies of their own. Modules can only modify their own internal state.
deployRentalSafe
reclaimRentalOrder()
can only be called from the rentalWallet. This is done through delegateCall from the Safe contract to the Stop.sol contract, such that the address(this) is the Safe (wallet) addressCreating a Safe
Stopping a Rent
execTransactionFromModule
method.execTransactionFromModule
cannot be called directly because the msg.sender must be part of a module (In this case, Stop is part of the module)execTransactionFromModule
Using Payment Escrow
The protocol places heavy emphasis on centralization. Policies are whitelisted, hooks are whitelisted, fees can be set up to 100%, upgrade and freeze of contract is done by the admin
Without admin intervention, protocol can still work but the functionalities will be limited.
Overall Centralization risk: High
Design Patterns and Best Practices:
Established design patterns, like modifiers are used properly. Interesting usage of the DEFAULT framework, splitting the architecture into modules and policies. Seems like everything is set up well, every policy contract has requestPermissions of the functions that they are using from the modules.
Good usage of errors. Clear and concise error messages.
Code Readability and Maintainability:
Code is hard to read because of 2 external integration, Seaport and Gnosis Safe, and also the usage of Default framework. A lot of understanding of proxy and delegatecall is needed to understand the protocol. Despite that, code is pretty easy to maintain, as there is not many entrance point and admin can easily upgrade / freeze contracts.
Error Handling and Input Validation:
Events and Error messages are easy to understand. Error messages are pretty intuitive. Try-catch statements are also used well. Inputs are validated well. Creating a rental, stopping a rental, admin fees all have good validation (eg no duplication of order has, no doubling of rental, no stopping of rental twice).
Validation of external integration is also well handled, like interacting with Seaport and Safe
Interoperability and Standards Compliance:
Complies well with Safe, ERC20, ERC721 and ERC1155. Also uses the UUPS EIP-1822 format. Hooks are also use to improve interoperability between external integrations
Testing and Documentation:
Test has decent coverage. There is no proper documentation, only some markdowns in the github itself. There should be a full overview of how everything works from start to end, how Seaport and Safe is integrated, how users can interact with the protocol and how users/lenders can benefit from the protocol.
Upgradeability:
Protocol is upgradeable. Uses EIP-1822.
Dependency Management:
High reliance on external libraries like OpenZeppelin, Seaport, Safe which increases attack vectors.
Overall, architecture from the protocol is complex but works well
25 hours
#0 - c4-judge
2024-01-27T20:23:46Z
0xean marked the issue as grade-b