Platform: Code4rena
Start Date: 03/11/2022
Pot Size: $115,500 USDC
Total HM: 17
Participants: 120
Period: 7 days
Judge: LSDan
Total Solo HM: 1
Id: 174
League: ETH
Rank: 25/120
Findings: 2
Award: $994.67
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xNazgul, 0xRoxas, 0xSmartContract, Awesome, Aymen0909, B2, BClabs, Bnke0x0, Deekshith99, Deivitto, Diana, Dinesh11G, Funen, HE1M, HardlyCodeMan, Josiah, Nyx, Rahoz, RaymondFam, RedOneN, ReyAdmirado, Rolezn, Saintcode_, TomJ, Trust, __141345__, a12jmx, adriro, ajtra, aphak5010, apostle0x01, brgltd, btk, bulej93, c3phas, carlitox477, catwhiskeys, ch0bu, chaduke, chrisdior4, cryptonue, cryptostellar5, csanuragjain, ctf_sec, delfin454000, djxploit, durianSausage, erictee, fatherOfBlocks, gogo, i_got_hacked, immeas, joestakey, jumpdest7d, lukris02, martin, mcwildy, merlin, minhquanym, oyc_109, pashov, peanuts, pedr02b2, rbserver, rotcivegaf, rvierdiiev, sakman, saneryee, seyni, shark, slowmoses, tnevler, trustindistrust, w0Lfrum, yurahod, zaskoh
61.3462 USDC - $61.35
The pragma version used are:
pragma solidity ^0.8.9; pragma solidity 0.8.9;
Note that mixing pragma is not recommended. Because different compiler versions have different meanings and behaviors, it also significantly raises maintenance costs. As a result, depending on the compiler version selected for any given file, deployed contracts may have security issues.
The minimum required version must be 0.8.17; otherwise, contracts will be affected by the following important bug fixes:
abi.encodeCall
in place of fixed bytes arguments.calldatasize()
in all cases.bytes
arrays.Apart from these, there are several minor bug fixes and improvements.
The code that contains "open todos" reflects that the development is not finished and that the code can change a posteriori, prior release, with or without audit.
Affected source code:
TODO: test
The following methods have a lack of checks if the received argument is an address, it's good practice in order to reduce human error to check that the address specified in the constructor or initialize is different than address(0)
.
Also, the EIP-165
standard helps detect that a smart contract implements the expected logic, prevents human error when configuring smart contract bindings, so it is recommended to check that the received argument is a contract and supports the expected interface.
Reference:
Affected source code:
The CreditLib.accrue
method says:
// interest will almost always be less than deposit // low risk of overflow unless extremely high interest rate
This low risk is not "risk free", so it doesn't make sense to have an unchecked region in this code.
function accrue( ILineOfCredit.Credit memory credit, bytes32 id, address interest ) public returns (ILineOfCredit.Credit memory) { unchecked { // interest will almost always be less than deposit // low risk of overflow unless extremely high interest rate // get token demoninated interest accrued uint256 accruedToken = IInterestRateCredit(interest).accrueInterest( id, credit.principal, credit.deposit ); // update credit line balance credit.interestAccrued += accruedToken; emit InterestAccrued(id, accruedToken); return credit; } }
Affected source code:
Because to transfer ether the .transfer
method (which is capped at 2300 gas) is used instead of .call
which is limited to the gas provided by the user. If a contract that has a fallback
method more expensive than 2300 gas, creates an offer, it could be impossible for this contract to send the funds to the buyer or seller.
Reference:
Affected source code:
Recommended Mitigation Steps:
Some parameters have not been commented, which reflects that the logic has been updated but not the documentation, it is advisable that developers update both at the same time to avoid the code being out of sync with the project documentation.
/** * see ILineOfCredit._createCredit * @notice called by LineOfCredit._createCredit during every repayment function + * @param id - Fill me + * @param amount - Fill me + * @param lender - Fill me + * @param token - Fill me * @param oracle - interset rate contract used by line that will calculate interest owed */ function create( bytes32 id, uint256 amount, address lender, address token, address oracle )
Affected source code:
The method removePosition
present inside the library CreditListLib
return true when the id
was not found, something that should be fixed in order to prevent a wrong use of this method.
Affected source code:
#0 - c4-judge
2022-12-06T21:47:18Z
dmvt marked the issue as grade-b
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xRajkumar, Awesome, Aymen0909, B2, Bnke0x0, Deivitto, Diana, JC, Metatron, Rahoz, RaymondFam, RedOneN, ReyAdmirado, Rolezn, Saintcode_, TomJ, __141345__, ajtra, aphak5010, brgltd, c3phas, ch0bu, chrisdior4, cryptonue, durianSausage, emrekocak, erictee, exolorkistis, gogo, karanctf, lukris02, martin, me_na0mi, oyc_109, peanuts, rotcivegaf, saneryee, seyni, tnevler, zaskoh
933.3247 USDC - $933.32
Using an updated version of solidity you can achieve significant gas savings, like the one mentioned above:
Using compound assignment operators for state variables (like State += X
or State -= X
...) it's more expensive than using operator assignment (like State = State + X
or State = State - X
...).
Proof of concept (without optimizations):
pragma solidity 0.8.15; contract TesterA { uint256 private _a; function testShort() public { _a += 1; } } contract TesterB { Uint256 private _a; function testLong() public { _a = _a + 1; } }
Gas saving executing: 13 per entry
TesterA.testShort: 43507 TesterB.testLong: 43494
Affected source code:
An empty block or an empty method generally indicates a lack of logic that it’s unnecessary and should be eliminated, call a method that literally does nothing only consumes gas unnecessarily, if it is a virtual
method which is expects it to be filled by the class that implements it, it is better to use abstract
contracts with just the definition.
Sample code:
pragma solidity >=0.4.0 <0.7.0; abstract contract BaseContract { function totalSupply() public virtual returns (uint256); }
Reference:
Affected source code:
Several methods have been found in which it is possible to reduce the number of mathematical operations, the cases are detailed below.
Affected source code:
Use scientific notation rather than exponentiation:
10**5
=> 1e5
: EscrowLib.sol:42 and EscrowLib.sol:42Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met.
Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
Proof of concept (without optimizations):
pragma solidity 0.8.15; contract TesterA { function testShortRevert(bool path) public view { require(path, "test error"); } } contract TesterB { function testLongRevert(bool path) public view { require(path, "test big error message, more than 32 bytes"); } }
Gas saving executing: 18 per entry
TesterA.testShortRevert: 21886 TesterB.testLongRevert: 21904
Affected source code:
Custom errors from Solidity 0.8.4
are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)
Source Custom Errors in Solidity:
Starting from Solidity v0.8.4
, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.
Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).
Proof of concept (without optimizations):
pragma solidity 0.8.15; contract TesterA { function testRevert(bool path) public view { require(path, "test error"); } } contract TesterB { error MyError(string msg); function testError(bool path) public view { if(path) revert MyError("test error"); } }
Gas saving executing: 9 per entry
TesterA.testRevert: 21611 TesterB.testError: 21602
Affected source code:
Having a method that always returns the same value is not correct in terms of consumption, if you want to modify a value, and the method will perform a revert
in case of failure, it is not necessary to return a true
, since it will never be false
. It is less expensive not to return anything, and it also eliminates the need to double-check the returned value by the caller.
Affected source code:
delete
optimizationUse delete
instead of set to default value (false
or 0
).
5 gas could be saved per entry in the following affected lines:
Affected source code:
unchecked
keywordWhen an underflow or overflow cannot occur, one might conserve gas by using the unchecked
keyword to prevent unnecessary arithmetic underflow/overflow tests.
Affected source code:
There is no need to create index for amount or values, these values will be different each time, and there are no reason to have an index on them.
event AddCredit( address indexed lender, address indexed token, uint256 indexed deposit, bytes32 id ); /// @notice Emits data re Lender removes funds (principal) - there is no corresponding function, just withdraw() event WithdrawDeposit(bytes32 indexed id, uint256 indexed amount); /// @notice Emits data re Lender withdraws interest - there is no corresponding function, just withdraw() // Bob - consider changing event name to WithdrawInterest event WithdrawProfit(bytes32 indexed id, uint256 indexed amount); /// @notice After accrueInterest runs, emits the amount of interest added to a Borrower's outstanding balance of interest due // but not yet repaid to the Line of Credit contract event InterestAccrued(bytes32 indexed id, uint256 indexed amount); // Borrower Events event Borrow(bytes32 indexed id, uint256 indexed amount); // Emits notice that a Borrower has drawn down an amount on a credit line event RepayInterest(bytes32 indexed id, uint256 indexed amount); /** Emits that a Borrower has repaid an amount of interest (N.B. results in an increase in interestRepaid, i.e. interest not yet withdrawn by a Lender). There is no corresponding function */ event RepayPrincipal(bytes32 indexed id, uint256 indexed amount); // Emits that a Borrower has repaid an amount of principal - there is no corresponding function
Affected source code:
#0 - c4-judge
2022-11-17T22:58:26Z
dmvt marked the issue as grade-b
#1 - c4-judge
2022-11-17T22:59:35Z
dmvt marked the issue as grade-a