Ajna Protocol - ReyAdmirado's results

A peer to peer, oracleless, permissionless lending protocol with no governance, accepting both fungible and non fungible tokens as collateral.

General Information

Platform: Code4rena

Start Date: 03/05/2023

Pot Size: $60,500 USDC

Total HM: 25

Participants: 114

Period: 8 days

Judge: Picodes

Total Solo HM: 6

Id: 234

League: ETH

Ajna Protocol

Findings Distribution

Researcher Performance

Rank: 49/114

Findings: 1

Award: $179.84

Gas:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

179.8397 USDC - $179.84

Labels

bug
G (Gas Optimization)
grade-a
G-20

External Links

1. expressions for constant values such as a call to keccak256(), should use immutable rather than constant

2. Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate(other than the known findings)

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key’s keccak256 hash (Gkeccak256 - 30 gas) and that calculation’s associated stack operations.

_distributions(#L69) and _quadraticVoters(#L94) and _isSurplusFundsUpdated(#L100) all get distributionId and they are internal so they can be combined to save gas.

hasClaimedReward(#L106) and screeningVotesCast(#L112) get distributionId and they are public so they can be combined to save gas.

3. state variables should be cached in stack variables rather than re-reading them from storage

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.

poolKey[tokenId_] can be cached before #L522. this will result in 2 less complex storage reads in #L523 and #L529

we can make a stack variable and put the answer to treasury + fundingAmount_ inside it so we can use that in lines 62 and 64 instead of reading the treasury with a SLOAD in #L64. this will also stop the usage of += which wastes gas in #L62.

_fundedExtraordinaryProposals.length can be cached to possibly save gas. _fundedExtraordinaryProposals.length is being checked in the if condition(#L208) and if it fails there is gonna be a extra read for it in #L213. so we can cache it before #L208 to save big amount of gas if the condition fails but lose only 3 gas if it passes

4. <x> += <y> costs more gas than <x> = <x> + <y> for state variables

Using the addition operator instead of plus-equals saves gas (13 or 113 each dependant on the usage see here)

5. ++i costs less gas than i++, especially when it’s used in for-loops (--i/i-- too)

Saves 5 gas per loop

6. can make the variable outside the loop to save gas

make the variable outside the loop and only give the value to variable inside

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

8. use the most recent version of solidity to save more gas

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.

9. using calldata instead of memory for read-only arguments in external functions saves gas

When 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.

10. using bool for storage incurs overhead

Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot's contents, replace the bits taken up by the boolean, and then write back. This is the compiler's defense against contract upgrades and pointer aliasing, and it cannot be disabled. Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past

11. Ternary over if ... else

Using ternary operator instead of the if else statement saves gas.

12. Use assembly to check for address(0)

saves 6 gas per instance

13. put the result of a keccak256() inside the constant not the operation

a constant expression in a variable will compute the expression every time the variable is called. It's not the result of the expression that is stored, but the expression itself. this will result in keccak256() operation being done but if we used the result this wouldnt happen

14. part of the code can be pre calculated

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.

0.9 can be used instead of 9 / 10 to stop a division operation

15. Using 10**X for constants isn't gas efficient

In Solidity, a constant expression in a variable will compute the expression every time the variable is called. It's not the result of the expression that is stored, but the expression itself.

As Solidity supports the scientific notation, constants of form 10**X can be rewritten as 1eX to save the gas cost from the calculation with the exponentiation operator **.

16. Non-strict inequalities are cheaper than strict ones

In the EVM, there is no opcode for non-strict inequalities (>=, <=) and two operations are performed (> + = or < + =). consider replacing >= with the strict counterpart > and add - 1 to the second side

#0 - c4-judge

2023-05-17T11:06:46Z

Picodes marked the issue as grade-a

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