Good Entry - ReyAdmirado's results

The best day trading platform to make every trade entry a Good Entry.

General Information

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

Good Entry

Findings Distribution

Researcher Performance

Rank: 37/80

Findings: 1

Award: $190.03

Gas:
grade-a

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Findings Information

Awards

190.0322 USDC - $190.03

Labels

bug
G (Gas Optimization)
grade-a
sponsor confirmed
G-14

External Links

1. State variables only set in the constructor should be declared immutable. (ones not caught by bot)

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

2. state variables can be packed into fewer storage slots

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.

3. state variables should be cached in stack variables rather than re-reading them from storage (ones not caught by bot)

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.

4. not using the named return variables when a function returns, wastes deployment gas

5. it costs more gas to initialize non-constant/non-immutable variables to zero than to let the default of zero be applied

6. require()/revert() strings longer than 32 bytes cost extra gas

Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas

7. require() or revert() statements that check input arguments should be at the top of the function

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

8. use custom errors rather than revert()/require() strings to save deployment gas

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/

9. using private rather than public for constants, saves gas

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

10. state var is defined but not used anywhere

even if they are for readability, consider making them comments instead. saves a lot of deplyment gas

11. Functions guaranteed to revert when called by normal users can be marked 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.

12. Optimize names to save gas

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

13. sorting the lines better will result in less storage access needed which result in gas save

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

14. Use hardcoded address instead address(this)

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

15. Unbounded Gas Consumption Risk Due to External Call Recipients

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.

16. Refactor If/require statements to save gas in case of early revert

Checks 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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Β© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter