Platform: Code4rena
Start Date: 08/05/2023
Pot Size: $90,500 USDC
Total HM: 17
Participants: 102
Period: 7 days
Judge: 0xean
Total Solo HM: 4
Id: 236
League: ETH
Rank: 89/102
Findings: 1
Award: $44.94
๐ Selected for report: 0
๐ Solo Findings: 0
๐ Selected for report: JCN
Also found by: 0xAce, 0xSmartContract, Aymen0909, K42, Rageur, Raihan, ReyAdmirado, SAAJ, SM3_SS, Sathish9098, Team_Rocket, Udsen, c3phas, codeslide, descharre, fatherOfBlocks, hunter_w3b, j4ld1na, lllu_23, matrix_0wl, naman1778, petrichor, pontifex, rapha, souilos, wahedtalash77
44.9387 USDC - $44.94
Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmaccess (100 gas) with a PUSH32 (3 gas).
Caching of a state variable replace each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.
kink
has the possibility to be read from storage 3 times. if we cache it before the #L179 check if the check passes we lose only 3 gas but if it fails we will save 197 gas.
for poolsAssetsReserves[comptroller][asset] -= amount
to happen a storage read on poolsAssetsReserves[comptroller][asset]
will happen but we can have it from before because its checked in the require above it as well. we can reduce one complex SLOAD if we cache it before the require #L72.
recoveredAmount_ <= badDebt
would not revert and badDebt gonna be read from storage again later so we can cache badDebt
before the require so we can save a highly possible 97 gas.
usually the check (#L1215) will fail and totalReserves
will be read from storage in both #L1215 and #L1223. so we can cache it before #L1215 to save 97 gas.
protocolShareReserve
is a address that is being read from storage 3 times. we can save 200 gas by caching it before
poolRegistry
is being read twice if the check in #L157 passes, which usually does. cache it before to save 97 gas
shortfall
is being read twice if the check in #L191 passes, which usually does. cache it before to save 97 gas
poolReserves[comptroller]
is being read twice if the check in #L192 passes, which usually does. cache it before to save gas
convertibleBaseAsset
will be read twice if underlyingAsset != convertibleBaseAsset
condition happens so we can cache it before the check to possibly save 97 gas by only risking losing 3 gas if the check fails.
if snapshot.totalCollateral <= minLiquidatableCollateral
happens then revert will happen and minLiquidatableCollateral
will be read from storage again. so we can cache it before the if check and risk losing just 3 gas but we will have a possible 97 gas save if the revert happens.
if snapshot.totalCollateral > minLiquidatableCollateral
happens then revert will happen and minLiquidatableCollateral
will be read from storage again. so we can cache it before the if check and risk losing just 3 gas but we will have a possible 97 gas save if the revert happens.
poolRegistry
is being read twice inside 2 different require checks which usually pass so we can cache it before and save 97 gas. if the first require fails we only lose 3 gas otherwise its 97 gas save.
<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesUsing the addition operator instead of plus-equals saves gas (13 or 113 each dependant on the usage see here)
This change saves 6 gas per instance. The optimization works until solidity version 0.8.13 where there is a regression in gas costs.
Use a solidity version of at least 0.8.15 because the conditions necessary for inlining are relaxed. Benchmarks show that the change significantly decreases the bytecode size (which impacts the deployment cost) while the effect on the runtime gas usage is smaller. Use a solidity version of at least 0.8.17 to get prevention of the incorrect removal of storage writes before calls to Yul functions that conditionally terminate the external EVM call; Simplify the starting offset of zero-length operations to zero. More efficient overflow checks for multiplication.
calldata
instead of memory
for read-only arguments in external functions saves gasWhen a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution.
RewardsDistributor.sol#L198-L200 3 instances
Using ternary operator instead of the if else statement saves gas.
Contracts are allowed to override their parentsโ functions and change the visibility from external to public and can save gas by doing so.
This will save 100 gas because it gets rid of a storage read and instead uses a argument that we already have which gives the same result
use newCloseFactorMantissa
instead of closeFactorMantissa
inside to save 100 gas
use initialExchangeRateMantissa_
instead of initialExchangeRateMantissa
use underlying_
instead of underlying
saves 6 gas per instance
we already have the cached version of rewardsDistributors.length
inside rewardsDistributorsLength
#L930 so we dont need to read rewardsDistributors.length
from storage again in #L940
value of rewardTokenAccrued[contributor]
is inside contributorAccrued
as well and wee can use that instead which will be way cheaper
for assetsReserves[asset] += balanceDifference
to happen a storage read on assetsReserves[asset]
will happen but we have it from before inside assetReserve
delete
assigning to zero uses more gas than using delete
, and they both assign variable to default value. so it is encouraged if the data is no longer needed use delete instead.
these parts of the code can be pre calculated and given to the contract as constants this will stop the use of extra operations even if its for code readability consider putting comments instead.
2**224
use the answer instead. otherwise every time this function is called gas is wasted calculating this
2**32
expScale / 2
constants will keep the expression and not the result so every time the halfExpScale
constant is used expScale / 2
will be calculated so just pre calculate it.
Saves deployment costs
require(_isStarted(auction)
is used 3 times
cache the result of those operations in new stack variables and then use those to assign values to state variables and use those ones inside emit to stop 400 gas usage
The current form of relative path import is not recommended for use because it can unpredictably pollute the namespace. Instead, the Solidity docs recommend specifying imported symbols explicitly.
A good example:
import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {SafeCastLib} from "solmate/utils/SafeCastLib.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {IProducer} from "src/interfaces/IProducer.sol"; import {GlobalState, UserState} from "src/Common.sol";
all contracts
#0 - c4-judge
2023-05-18T17:41:38Z
0xean marked the issue as grade-b