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: 15/47
Findings: 2
Award: $352.77
π Selected for report: 0
π Solo Findings: 0
π Selected for report: cmichel
Also found by: AuditsAreUS, IllIllI, Ruhum, csanuragjain, danb, joshie, t11s, tintin
293.89 USDC - $293.89
https://github.com/code-423n4/2022-04-backed/blob/e8015d7c4b295af131f017e646ba1b99c8f608f0/contracts/NFTLoanFacilitator.sol#L148 https://github.com/code-423n4/2022-04-backed/blob/e8015d7c4b295af131f017e646ba1b99c8f608f0/contracts/NFTLoanFacilitator.sol#L174
Creating a loan with a minimum amount, instead of a fixed/maximum amount may lead to forcing the borrower to pay more interest than he was expecting or hoping for. The user never knows the potential maximum amount he will need to pay, until the loan is actually completed/aborted. Neither does he have the option to set it out.
Example:
Scenario A
Scenario B
Things to consider:
nft_floor - lowest_profit_margin_on_seizure
NFTLoanFacilitator:148
require(amount >= loan.loanAmount, 'NFTLoanFacilitator: amount too low');
NFTLoanFacilitator:174
require((previousLoanAmount * requiredImprovementRate / SCALAR) <= amountIncrease
One of the following:
#0 - wilsoncusack
2022-04-06T18:16:47Z
This is how the protocol is designed to work and we do not intend to change it
#1 - wilsoncusack
2022-04-06T20:04:06Z
#30
#2 - gzeoneth
2022-04-15T13:38:20Z
Duplicate of #24
π 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
58.883 USDC - $58.88
-- 1 All functions that receive uints which are not uint256 could be changed to uint256 and only casted to the intended size when storing it. More info at: https://github.com/ourzora/v3/pull/125#issuecomment-1034238815
Just swapped the lend()
function and consequent related tests resulting in: (...) Overall gas change: -11839 (-0.041%)
But there are many other functions (eg. createLoan
) which could use such change, so the gas saving should be way more than that.
-- 2
cache borrowTicketContract
at createLoan
to save one SLOAD on a successful transaction.
-- 3
use cached previousInterestRate
on calling _interestOwed
Overall gas change: -60 (-0.000%)
diff --git a/contracts/NFTLoanFacilitator.sol b/contracts/NFTLoanFacilitator.sol index 46d6ef5..bb25958 100644 --- a/contracts/NFTLoanFacilitator.sol +++ b/contracts/NFTLoanFacilitator.sol @@ -165,6 +165,7 @@ contract NFTLoanFacilitator is Ownable, INFTLoanFacilitator { // will underflow if amount < previousAmount uint256 amountIncrease = amount - previousLoanAmount; + uint256 accumulatedInterest; { uint256 previousInterestRate = loan.perAnumInterestRate; uint256 previousDurationSeconds = loan.durationSeconds; @@ -177,14 +178,14 @@ contract NFTLoanFacilitator is Ownable, INFTLoanFacilitator { || (previousInterestRate != 0 // do not allow rate improvement if rate already 0 && previousInterestRate - (previousInterestRate * requiredImprovementRate / SCALAR) >= interestRate), "NFTLoanFacilitator: proposed terms must be better than existing terms"); - } - uint256 accumulatedInterest = _interestOwed( - previousLoanAmount, - loan.lastAccumulatedTimestamp, - loan.perAnumInterestRate, - loan.accumulatedInterest - ); + = _interestOwed( + previousLoanAmount, + loan.lastAccumulatedTimestamp, + previousInterestRate, + loan.accumulatedInterest + ); + } require(accumulatedInterest <= type(uint128).max, "NFTLoanFacilitator: accumulated interest exceeds uint128");
-- 4
cache loan.loanAmount
on repayAndCloseLoan
testRepayInterestOwedExceedingUint128() (gas: -253 (-0.001%)) testRepayAndCloseSuccessful() (gas: -253 (-0.001%)) testRepayAndClose() (gas: -253 (-0.003%)) Overall gas change: -759 (-0.004%)
diff --git a/contracts/NFTLoanFacilitator.sol b/contracts/NFTLoanFacilitator.sol index f1f570f..ff2e3f7 100644 --- a/contracts/NFTLoanFacilitator.sol +++ b/contracts/NFTLoanFacilitator.sol @@ -231,23 +231,24 @@ contract NFTLoanFacilitator is Ownable, INFTLoanFacilitator { /// See {INFTLoanFacilitator-repayAndCloseLoan}. function repayAndCloseLoan(uint256 loanId) external override notClosed(loanId) { Loan storage loan = loanInfo[loanId]; + uint256 _loanAmount = loan.loanAmount; uint256 interest = _interestOwed( - loan.loanAmount, + _loanAmount, loan.lastAccumulatedTimestamp, loan.perAnumInterestRate, loan.accumulatedInterest ); address lender = IERC721(lendTicketContract).ownerOf(loanId); loan.closed = true; - ERC20(loan.loanAssetContractAddress).safeTransferFrom(msg.sender, lender, interest + loan.loanAmount); + ERC20(loan.loanAssetContractAddress).safeTransferFrom(msg.sender, lender, interest + _loanAmount); IERC721(loan.collateralContractAddress).safeTransferFrom( address(this), IERC721(borrowTicketContract).ownerOf(loanId), loan.collateralTokenId ); - emit Repay(loanId, msg.sender, lender, interest, loan.loanAmount); + emit Repay(loanId, msg.sender, lender, interest, _loanAmount); emit Close(loanId); }
#0 - wilsoncusack
2022-04-07T12:54:03Z
#1 - wilsoncusack
2022-04-10T16:06:04Z