Platform: Code4rena
Start Date: 21/06/2022
Pot Size: $55,000 USDC
Total HM: 29
Participants: 88
Period: 5 days
Judge: gzeon
Total Solo HM: 7
Id: 134
League: ETH
Rank: 47/88
Findings: 2
Award: $142.95
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: defsec
Also found by: 0x1f8b, 0x29A, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 0xkowloon, 0xmint, Bnke0x0, BowTiedWardens, Chom, ElKu, Funen, GalloDaSballo, GimelSec, IllIllI, JC, Kenshin, Kulk0, Lambda, Limbooo, MadWookie, Metatron, Picodes, Soosh, StErMi, TomJ, WatchPug, Waze, Yiko, _Adam, ak1, asutorufos, aysha, bardamu, catchup, datapunk, delfin454000, dipp, fatherOfBlocks, grGred, hake, hansfriese, hyh, joestakey, kebabsec, kenzo, kirk-baird, oyc_109, pashov, poirots, rfa, robee, saian, sashik_eth, shenwilly, simon135, slywaters, z3s, zeesaw, zer0dot
63.9425 USDC - $63.94
Non critical:
type(uint256).max)
instead of 2**256 - 1
I suggest using type(uint256).max)
instead of 2**256 - 1
in order to improve code readability.Line References: https://github.com/code-423n4/2022-06-illuminate/blob/main/lender/Lender.sol#L84 https://github.com/code-423n4/2022-06-illuminate/blob/main/lender/Lender.sol#L112
* We have followed general OpenZeppelin guidelines: functions revert instead * of returning `false` on failure. This behavior is nonetheless conventional * and does not conflict with the expectations of ERC20 applications.
I suggest changing:
/// @return bool true if the address was set, false otherwise
to just:
/// @return bool true if the address was set
Line References: https://github.com/code-423n4/2022-06-illuminate/blob/main/lender/Lender.sol#L77 https://github.com/code-423n4/2022-06-illuminate/blob/main/lender/Lender.sol#L144 https://github.com/code-423n4/2022-06-illuminate/blob/main/lender/Lender.sol#L166 https://github.com/code-423n4/2022-06-illuminate/blob/main/redeemer/Redeemer.sol#L69 https://github.com/code-423n4/2022-06-illuminate/blob/main/redeemer/Redeemer.sol#L80
🌟 Selected for report: BowTiedWardens
Also found by: 0v3rf10w, 0x1f8b, 0x29A, 0xKitsune, 0xNazgul, 0xf15ers, 0xkatana, 0xkowloon, Bnke0x0, ElKu, Fitraldys, Funen, GalloDaSballo, IllIllI, JC, Kaiziron, Lambda, MadWookie, Noah3o6, Nyamcil, RoiEvenHaim, TomJ, Tomio, UnusualTurtle, Waze, _Adam, ajtra, asutorufos, bardamu, c3phas, catchup, datapunk, defsec, delfin454000, fatherOfBlocks, grGred, hake, hansfriese, hyh, ignacio, joestakey, kebabsec, ladboy233, oyc_109, pashov, poirots, rfa, robee, sach1r0, samruna, sashik_eth, simon135, slywaters, z3s, zer0dot
79.0114 USDC - $79.01
constant
to immutable
Use of constant keccak variables results in extra hashing (and so gas). This results in the keccak operation being performed whenever the variable is used, increasing gas costs relative to just storing the output hash. Changing to immutable will only perform hashing on contract deployment which will save gas. You should use immutables until the referenced issues are implemented, then you only pay the gas costs for the computation at deploy time.Example:
uint256 immutable immutableUint; uint256 constant CONST_UINT = 999999999999999999999999999999; constructor(){ immutableUint = 999999999999999999999999999999; } function retrieveUintConst() external view returns (uint256){ return CONST_UINT; // 21244 gas } function retrieveUintImmutable() external view returns (uint256){ return immutableUint; // 21211 gas }
As you can see on the compiler version 0.8.15 and with optimizator on 200 runs immutables are cheaper, and saves you about 30 gas.
See: (ethereum/solidity#9232 (comment))
Line References: https://github.com/code-423n4/2022-06-illuminate/blob/main/lender/Lender.sol#L23
i++
or i += 1
++i
costs less gas compared to i++
or i += 1
for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.Example: i++
increments i and returns the initial value of i. Which means:
uint i = 1; i++; // == 1 but i == 2
But ++i
returns the actual incremented value:
uint i = 1; ++i; // == 2 and i == 2 too, so no need for a temporary variable
In the first case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2
Line References: https://github.com/code-423n4/2022-06-illuminate/blob/main/lender/Lender.sol#L96 https://github.com/code-423n4/2022-06-illuminate/blob/main/lender/Lender.sol#L120 https://github.com/code-423n4/2022-06-illuminate/blob/main/lender/Lender.sol#L289 https://github.com/code-423n4/2022-06-illuminate/blob/main/marketplace/ERC20Permit.sol#L64
uint256
to uint8
costs addiional gasExample 0.8.13
with optimizator enabled on 200:
contract Approves { uint public gas; function approve1(address token) external returns (bool) { gas = gasleft(); // max is the maximum integer value for a 256 unsighed integer uint256 max = 2**256 - 1; // approve the underlying for max per given principal for (uint8 i; i < 9; ) { // 73370 // check that the token is defined for this particular market if (token != address(0)) { // max approve the token Safe.approve(IERC20(token), address(this), max); } unchecked { ++i; } } gas -= gasleft(); // 73370 gas (from 0 to max) return true; } function approve2(address token) external returns (bool) { gas = gasleft(); // max is the maximum integer value for a 256 unsighed integer uint256 max = 2**256 - 1; // approve the underlying for max per given principal for (uint256 i; i < 9; ) { // check that the token is defined for this particular market if (token != address(0)) { // max approve the token Safe.approve(IERC20(token), address(this), max); } unchecked { ++i; } } gas -= gasleft(); // 73310 gas (from 0 to max) return true; } }
In this case it's not needed to make additional type conversion. It will save additional 60 gas.
Line References: https://github.com/code-423n4/2022-06-illuminate/blob/main/lender/Lender.sol#L87
calldata
instead of memory
for function parameters
In some cases, having function arguments in calldata
instead of memory
is more optimal. When arguments are read-only on external functions, the data location should be calldata
.Example:
contract C { function add(uint[] memory arr) external returns (uint sum) { uint length = arr.length; for (uint i = 0; i < arr.length;) { sum += arr[i]; unchecked { ++i; } } } }
In the above example, the dynamic array arr has the storage location memory
. When the function gets called externally, the array values are kept in calldata
and copied to memory
during ABI decoding (using the opcode calldataload
and mstore
). And during the for loop, arr[i]
accesses the value in memory
using a mload
. However, for the above example this is inefficient. Consider the following snippet instead:
contract C { function add(uint[] calldata arr) external returns (uint sum) { uint length = arr.length; for (uint i = 0; i < arr.length;) { sum += arr[i]; unchecked { ++i; } } } }
In the above snippet, instead of going via memory
, the value is directly read from calldata
using calldataload
. That is, there are no intermediate memory
operations that carries this value.
Gas savings: In the former example, the ABI decoding begins with copying value from calldata
to memory
in a for loop. Each iteration would cost at least 60 gas. In the latter example, this can be completely avoided. This will also reduce the number of instructions and therefore reduces the deploy time cost of the contract.
Line References: https://github.com/code-423n4/2022-06-illuminate/blob/main/lender/Lender.sol#L251 https://github.com/code-423n4/2022-06-illuminate/blob/main/marketplace/MarketPlace.sol#L70