Platform: Code4rena
Start Date: 01/08/2023
Pot Size: $91,500 USDC
Total HM: 14
Participants: 80
Period: 6 days
Judge: gzeon
Total Solo HM: 6
Id: 269
League: ETH
Rank: 37/80
Findings: 1
Award: $190.03
π Selected for report: 0
π Solo Findings: 0
π Selected for report: JCK
Also found by: 0xAnah, 0xhex, 0xta, DavidGiladi, K42, Rageur, Raihan, ReyAdmirado, Rolezn, SAQ, SY_S, Sathish9098, dharma09, hunter_w3b, matrix_0wl, naman1778, petrichor, wahedtalash77
190.0322 USDC - $190.03
Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmaccess (100 gas) with a PUSH32 (3 gas). While strings are not value types, and therefore cannot be immutable/constant if not hard-coded outside of the constructor, the same behavior can be achieved by making the current contract abstract with virtual functions for the string accessors, and having a child contract override the functions with the hard-coded implementation-specific values.
token0
token1
ROEROUTER
although for these ones they are turned into state variables to fix for coverage testing, make sure to turn them into immutable after the bug is checked or resolved
If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables are also cheaper.
bring isEnabled
(#L50) and baseTokenIsToken0
(#L64) which are booleans together with treasury
(#L58) to put them into one slot instead of current 3. both bools have 1 access with treasury
resulting in some gas save for accessing them.
bring liquidity
(#L52) to lowerTick
(#L28). 1 less slot used and also there is accesses to liquidity
and lowerTick
or upperTick
or feeTier
in 4 places, so the accesses gonna be cheaper.
The instances below point to the second+ access of a state variable within a function. 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.
ticks.length
can be cached before #L151 because its at least read twice from storage (#L151 and #L153) and if ticks.length > 2
coniditon be true, its gonna be read from storage 3 times every iteration of the loop. potentially saves a lot of gas. (make sure to adjust the variable we make after the push #L151)
token0
can be cached before #L251 to save 97 gas. its read from storage in both #L251 and #L266.(a better version of this is to cache all of this token == address(token0)
to have one less comparison and cast but it reduces readability)
baseFeeX4
will be read more than twice without changing value. cache it before #L451. saves at least 197 gas or possibly 397 gas.
address(tokenisedRanges[step])
can be cached before #L114 to reduce 2 complex SLOADs. used in #L114 and #L118 and #L120
address(tokenisedTicker[step])
can be cached before #L126 to reduce 2 complex SLOADs. used in #L126 and #L130 and #L132
address(ASSET_0)
can be cached before #L154 to reduce on SLOAD saving 97 gas. used in #L154 and #L155
address(ASSET_1)
can be cached before #L159 to reduce on SLOAD saving 97 gas. used in #L159 and #L160
fee0
can be cached before #L241 to at least save 97 gas(if the condition in #L241 is true so we save 297 gas). for this we need to change the #L243 from += into equals and use the cached version instead of state var.
fee1
can be cached before #L242 to at least save 97 gas(if the condition in #L242 is true so we save 297 gas). for this we need to change the #L244 from += into equals and use the cached version instead of state var.
TOKEN0.decimals
can be cached before #L274 to save gas. its used in #L274 and #L275. (a better version can be caching all this TOKEN0_PRICE / 10 ** TOKEN0.decimals
to not do calculations again)
TOKEN1.decimals
can be cached before #L274 to save gas. its used in #L274 and #L275. (a better version can be caching all this TOKEN1_PRICE / 10 ** TOKEN1.decimals
to not do calculations again)
liquidity
can be cached before #L237 to at least save 97 gas (possibly 197 if the condition in #L237 be true). used in #L278 and #L279 and a possible #L240.
Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas
Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas*) in a function that may ultimately revert in the unhappy case.
the require in #L218 is cheaper than all above it so we can put it first for possible gas save
the require in #L252 is cheaper than all above it so we can put it first for possible gas save
Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time theyβre hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas
https://blog.soliditylang.org/2021/04/21/custom-errors/
If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where itβs used, and not adding another entry to the method ID table
even if they are for readability, consider making them comments instead. saves a lot of deplyment gas
payable
If a function modifier or require such as onlyOwner-admin is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2), DUP1(3), ISZERO(3), PUSH2(3), JUMPI(10), PUSH1(3), DUP1(3), REVERT(0), JUMPDEST(1), POP(2) which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
Contracts most called functions could simply save gas by function ordering via Method ID. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions.
See more here. you can use this tool to get the optimized version for function and properties signitures
after we sort these lines like this, less gas will be used for storage access because some of these variables are inside one slot of storage
after the changes mentioned before here:
bring #L89 near #L92 because both treasury
and baseTokenIsToken0
are inside one slot so it takes less gas to assign to them
Instead of using address(this)
, it is more gas-efficient to pre-calculate and use the hardcoded address. Foundry's script.sol
and solmate's LibRlp.sol
contracts can help achieve this. - Reference
In the context of Solidity, external function calls without a specified gas limit present a significant risk. The callee contract has the potential to consume all the gas allocated to the transaction, causing an undesired revert and disrupt the function's execution. To mitigate this, it's recommended to explicitly set a gas limit when performing external calls using addr.call
{gas: }. This limits the gas forwarded to the callee, preventing potential pitfalls and offering better control over transaction execution.
If/require
statements to save gas in case of early revertChecks that involve calldata should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before using excessive gas in a call that may ultimately revert in an unhappy case.
first part of the if
has 2 SLOADs and because there is && in between the if conditions putting the first part as last will have a chance of saving gas by doing one less SLOAD. put fee0+fee1 > 0
check last one to be checked
#0 - c4-judge
2023-08-20T17:03:03Z
gzeon-c4 marked the issue as grade-a
#1 - c4-sponsor
2023-08-23T14:27:14Z
Keref marked the issue as sponsor confirmed
#2 - Keref
2023-08-23T14:27:19Z
Implemented in part