Platform: Code4rena
Start Date: 05/04/2022
Pot Size: $30,000 USDC
Total HM: 10
Participants: 47
Period: 3 days
Judge: gzeon
Total Solo HM: 4
Id: 106
League: ETH
Rank: 16/47
Findings: 3
Award: $302.20
π Selected for report: 0
π Solo Findings: 0
139.9476 USDC - $139.95
https://github.com/code-423n4/2022-04-backed/tree/main/contracts/NFTLoanFacilitator.sol#L155
There are ERC20 tokens that charge fee for every transfer() / transferFrom().
Vault.sol#addValue() assumes that the received amount is the same as the transfer amount, and uses it to calculate attributions, balance amounts, etc. But, the actual transferred amount can be lower for those tokens. Therefore it's recommended to use the balance change before and after the transfer instead of the amount. This way you also support the tokens with transfer fee - that are popular.
https://github.com/code-423n4/2022-04-backed/tree/main/contracts/NFTLoanFacilitator.sol#L155
#0 - wilsoncusack
2022-04-06T18:19:28Z
We do not support special logic for these tokens. Borrowers/lenders should know what is implied by the assets they are using
#1 - gzeoneth
2022-04-15T12:43:51Z
Duplicate of #75
π Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xkatana, BouSalman, CertoraInc, Dravee, FSchmoede, Hawkeye, Kenshin, Meta0xNull, PPrieditis, Ruhum, TerrierLover, VAD37, WatchPug, berndartmueller, csanuragjain, hake, horsefacts, hubble, m9800, rayn, reassor, robee, samruna, securerodd, shenwilly, sorrynotsorry, t11s, teryanarmen, tintin, z3s
67.476 USDC - $67.48
Title: In the following public update functions no value is returned Severity: Low Risk
In the following functions no value is returned, due to which by default value of return will be 0. We assumed that after the update you return the latest new value. (similar issue here: https://github.com/code-423n4/2021-10-badgerdao-findings/issues/85).
NFTLoanFacilitator.sol, updateOriginationFeeRate NFTLoanFacilitator.sol, updateRequiredImprovementRate
Title: Does not validate the input fee parameter Severity: Low Risk
Some fee parameters of functions are not checked for invalid values. Validate the parameters:
NFTLoanFacilitator.updateOriginationFeeRate (_originationFeeRate)
Title: Add a timelock Severity: Low Risk
To give more trust to users: functions that set key/critical variables should be put behind a timelock.
https://github.com/code-423n4/2022-04-backed/tree/main/contracts/NFTLoanFacilitator.sol#L279 https://github.com/code-423n4/2022-04-backed/tree/main/contracts/NFTLoanFacilitator.sol#L289
Title: Mult instead div in compares Severity: Low Risk
To improve algorithm precision instead using division in comparison use multiplication in the following scenario: Instead a < b / c use a * c < b. In all of the big and trusted contracts this rule is maintained. NFTLoanFacilitator.sol, 177, && previousInterestRate - (previousInterestRate * requiredImprovementRate / SCALAR) >= interestRate), "NFTLoanFacilitator: proposed terms must be better than existing terms"); NFTLoanFacilitator.sol, 175, || previousDurationSeconds + (previousDurationSeconds * requiredImprovementRate / SCALAR) <= durationSeconds NFTLoanFacilitator.sol, 174, require((previousLoanAmount * requiredImprovementRate / SCALAR) <= amountIncrease
Title: Check transfer receiver is not 0 to avoid burned money Severity: Low Risk
Transferring tokens to the zero address is usually prohibited to accidentally avoid "burning" tokens by sending them to an unrecoverable zero address.
https://github.com/code-423n4/2022-04-backed/tree/main/contracts/NFTLoanFacilitator.sol#L155 https://github.com/code-423n4/2022-04-backed/tree/main/contracts/LendTicket.sol#L48 https://github.com/code-423n4/2022-04-backed/tree/main/contracts/LendTicket.sol#L21 https://github.com/code-423n4/2022-04-backed/tree/main/contracts/NFTLoanFacilitator.sol#L262 https://github.com/code-423n4/2022-04-backed/tree/main/contracts/NFTLoanFacilitator.sol#L88
Title: Override function but with different argument location Severity: Low/Med Risk
BorrowTicket.sol.constructor inherent NFTLoanTicket.sol.constructor but the parameters does not match LendTicket.sol.constructor inherent NFTLoanTicket.sol.constructor but the parameters does not match
Title: transfer return value of a general ERC20 is ignored Severity: Low: Can be even High Risk
Need to use safeTransfer instead of transfer. As there are popular tokens, such as USDT that transfer/trasnferFrom method doesnβt return anything. The transfer return value has to be checked (as there are some other tokens that returns false instead revert), that means you must
Check the transfer return value Another popular possibility is to add a whiteList. Those are the appearances (solidity file, line number, actual line):
NFTLoanFacilitator.sol, 88 (createLoan), IERC721(collateralContractAddress).transferFrom(msg.sender, address(this), collateralTokenId);
Title: Not verified input Severity: Low Risk
external / public functions parameters should be validated to make sure the address is not 0. Otherwise if not given the right input it can mistakenly lead to loss of user funds. NFTLoanFacilitator.sol.withdrawOriginationFees asset NFTLoanFacilitator.sol.createLoan loanAssetContractAddress NFTLoanFacilitator.sol.lend sendLendTicketTo LendTicket.sol.loanFacilitatorTransfer to NFTLoanFacilitator.sol.setLendTicketContract _contract
#0 - wilsoncusack
2022-04-06T19:25:08Z
In the following public update functions no value is returned
won't change
Add a timelock
these can only be set once, doesn't make sense to me
Mult instead div in compares
need more detail, I think we are doing this? help wanted
Check transfer receiver is not 0 to avoid burned money
155 - not relevant 48 - is prevented on 37, and even this is not needed because we know the ownerOf lend ticket in this case is no address(0) 21 - same as above 267 - transfer to address(0) will be blocked by LoanTicket which is Solmate erc721 which blocks transfers to 0. Also not possible borrowTicker owner is address 0 88 - not relevant, is transferring to self
Override function but with different argument location
dispute, need more details
transfer return value of a general ERC20 is ignored
dispute, line given is an ERC721 transfer
Not verified input
asset - call will fail / no-op if call is not correct loanAssetContractAddress - legit, we check that it is not address(0) in lend but we should also check that the code length is not 0. sendLendTicketTo - user is responsible, but address(0) will revert to - address(0) is checked _contract - won't fix
π Selected for report: Dravee
Also found by: 0v3rf10w, 0x1f8b, 0xkatana, CertoraInc, FSchmoede, Funen, IllIllI, Kenshin, Meta0xNull, TerrierLover, Tomio, csanuragjain, joshie, obront, rayn, rfa, robee, saian, securerodd, sorrynotsorry, t11s, z3s
94.7681 USDC - $94.77
Title: Use calldata instead of memory Severity: GAS
Use calldata instead of memory for function parameters In some cases, having function arguments in calldata instead of memory is more optimal.
NFTLoanTicket.constructor (name) NFTLoanTicket.constructor (symbol)
Title: Short the following require messages Severity: GAS
The following require messages are of length more than 32 and we think are short enough to short them into exactly 32 characters such that it will be placed in one slot of memory and the require function will cost less gas. The list:
Solidity file: NFTLoanFacilitator.sol, In line 258, Require message length to shorten: 39, The message: NFTLoanFacilitator: payment is not late Solidity file: NFTLoanFacilitator.sol, In line 82, Require message length to shorten: 33, The message: NFTLoanFacilitator: 0 loan amount Solidity file: NFTLoanFacilitator.sol, In line 321, Require message length to shorten: 38, The message: NFTLoanFacilitator: 0 improvement rate
Title: Use != 0 instead of > 0 Severity: GAS
Using != 0 is slightly cheaper than > 0. (see https://github.com/code-423n4/2021-12-maple-findings/issues/75 for similar issue)
NFTLoanFacilitator.sol, 321: change '_improvementRate > 0' to '_improvementRate != 0'
Title: Storage double reading. Could save SLOAD Severity: GAS
Reading a storage variable is gas costly (SLOAD). In cases of multiple read of a storage variable in the same scope, caching the first read (i.e saving as a local variable) can save gas and decrease the overall gas uses. The following is a list of functions and the storage variables that you read twice:
NFTLoanFacilitator.sol: originationFeeRate is read twice in lend
Title: Internal functions to private Severity: GAS
The following functions could be set private to save gas and improve code quality:
LendTicket.sol, _transfer NFTLoanFacilitator.sol, _interestOwed
Title: Unnecessary constructor Severity: GAS
The following constructors are empty. (A similar issue https://github.com/code-423n4/2021-11-fei-findings/issues/12)
LendTicket.sol.constructor BorrowTicket.sol.constructor
Title: Use unchecked to save gas for certain additive calculations that cannot overflow Severity: GAS
You can use unchecked in the following calculations since there is no risk to overflow:
NFTLoanFacilitator.sol (L#382) - return loanAmount * (block.timestamp - lastAccumulatedTimestamp) * (perAnumInterestRate * 1e18 / 365 days) / 1e21 + accumulatedInterest; NFTLoanFacilitator.sol (L#258) - require(block.timestamp > loan.durationSeconds + loan.lastAccumulatedTimestamp, "NFTLoanFacilitator: payment is not late");
Title: Rearrange state variables Severity: GAS
You can change the order of the storage variables to decrease memory uses.
In NFTLoanFacilitator.sol,rearranging the storage fields can optimize to: 6 slots from: 7 slots. The new order of types (you choose the actual variables): 1. uint256 2. uint256 3. uint256 4. uint256 5. address 6. uint8 7. address
Title: Change transferFrom to transfer Severity: GAS
'transferFrom(address(this), , **)' could be replaced by the following more gas efficient 'transfer(, **)' This replacement is more gas efficient and improves the code quality.
NFTLoanFacilitator.sol, 124 : IERC721(loan.collateralContractAddress).transferFrom(address(this), sendCollateralTo, loan.collateralTokenId);
Title: Inline one time use functions Severity: GAS
The following functions are used exactly once. Therefore you can inline them and save gas and improve code clearness.
LendTicket.sol, _transfer
Title: Consider inline the following functions to save gas Severity: GAS
You can inline the following functions instead of writing a specific function to save gas. (see https://github.com/code-423n4/2021-11-nested-findings/issues/167 for a similar issue.) NFTLoanFacilitator.sol, _interestOwed, { return loanAmount * (block.timestamp - lastAccumulatedTimestamp) * (perAnumInterestRate * 1e18 / 365 days) / 1e21 // SCALAR * 1e18 + accumulatedInterest; }
Title: Public functions to external Severity: GAS
The following functions could be set external to save gas and improve code quality. External call cost is less expensive than of public functions.
NFTLoanTicket.sol, tokenURI
#0 - wilsoncusack
2022-04-06T19:56:27Z
Use calldata instead of memory
ack, do not care about optimizing this
Short the following require messages
In my testing, this does not save any guess. Can someone confirm? I also closed an issue with this. Help
Use != 0 instead of > 0
ack, do not care about optimizing this really
Storage double reading. Could save SLOAD
There is no path on which originationFeeRate is read twice from storage, that I can tell.
Internal functions to private
does not save gas in my testing
Unnecessary constructor
we need this constructor to call to the parent constructor and to not be an abstract contract? help
Use unchecked to save gas for certain additive calculations that cannot overflow
ack, need to validate these can't overflow.
Rearrange state variables
ack, do not care much about optimizing deploy cost
Change transferFrom to transfer
transfer is not an ERC721 function
Inline one time use functions
I like the clarity of what is from solmate, but will consider
Consider inline the following functions to save gas
like having this on its own
Public functions to external
needs to be public to override
#1 - wilsoncusack
2022-04-15T13:57:54Z
removing help wanted, shortening messages does indeed help in that it frees up byte space for the optimizer