Platform: Code4rena
Start Date: 31/01/2023
Pot Size: $90,500 USDC
Total HM: 47
Participants: 169
Period: 7 days
Judge: LSDan
Total Solo HM: 9
Id: 211
League: ETH
Rank: 24/169
Findings: 1
Award: $1,023.26
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: c3phas
Also found by: 0xSmartContract, 0xackermann, 0xdaydream, Aymen0909, CodingNameKiki, Dewaxindo, Diana, IllIllI, Madalad, NoamYakov, Pheonix, Polaris_tow, ReyAdmirado, Rolezn, arialblack14, atharvasama, cryptostellar5, descharre, eyexploit, lukris02, saneryee
1023.2634 USDC - $1,023.26
Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmaccess (100 gas) with a PUSH32 (3 gas).
feeRecipient
can be declared immutable
adminProxy
vaultRegistry
permissionRegistry
escrow
Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings
put account
near the start
so the 3 uint32 variables(12 byte) and the address variable(20 bytes) be in the same slot(32byte). this will save one slot usage
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.
_decimals
(#L38) and feeRecipient
(#L510) can be put together in the same slot if they be declared near each other. there is no reads that uses both variable so mainly saves Gsset of 20000 for a slot. bring the feeRecipient
near _decimals
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.
templateExists
and templateCategoryExists
can be simplified to use less slots. used together once so it will save a bit gas there too.
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.
possible 97 gas save with risking 3 gas. if the check fails we save 97 for the second quitPeriod
read and if it passes we just lose 3 gas. cache it before this line
possible 97 gas save with risking 3 gas. if the check fails we save 97 for the second quitPeriod
read and if it passes we just lose 3 gas. cache it before this line
caching deploymentController
will save 97 gas. its being used twice in #L837 and #L840 and can be cached before it.
cache yVault.totalSupply()
before the if statement because it will usually be false and we will save big gas for a risk of losing 3 gas if the check be true. used twice in #L90 and #L95. should be cached before this line
cache VaultAPI(IYearnRegistry(externalRegistry).latestVault(_asset))
and assign the cached version to yVault
in #L45 so we can use the cached version in #L54 to save gas.
caching adapter
will save 97 gas. adapter
is being used twice in #L604 and #L606, we can reduce one SLOAD with caching it before #L604.(it changes after #L608 so the #L610 adapter
should be cached version of proposedAdapter
that I explain after this one)
the proposedAdapter
is being used in both #L606 and #L608 and can be cached before to save gas as c4udit found, but adapter
in line 610 can be the cached version of proposedAdapter
instead. this will save 100 more gas. cache before #L606
unchecked {}
for subtractions where the operands cannot underflow because of a previous require()
or if
statementrequire(a <= b); x = b - a => require(a <= b); unchecked { x = b - a } if(a <= b); x = b - a => if(a <= b); unchecked { x = b - a } this will stop the check for overflow and underflow so it will save gas
<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesUsing the addition operator instead of plus-equals saves gas
make the variable outside and only give the value to variable inside
success
and returnData
7 varibles here
escrowId
and escrow
and claimable
rewardAmount
and escrowInfo
rewardToken
and rewards
++i/i++
should be unchecked{++i}/unchecked{i++}
when it is not possible for them to overflow, as is the case when used in for-loop and while-loopsThe unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas PER LOOP
use a strict version of solidity
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.
Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.
_registerCreatedVault
_handleVaultStakingRewards
__deployAdapter
_encodeAdapterData
_deployStrategy
_registerVault
_verifyAdapterConfiguration
_shareValue
_freeFunds
_yTotalAssets
_calculateLockedProfit
_lockToken
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size. Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
when using smaller versions of types like uint or bytes wouldnt help us to send stuff in one slot(like for a struct) its better to just use the 256 bits version of types to not waste gas.
many places for len
and i
in
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 near 97 gas
instead of asset
asset_
can be used
bytes4(keccak256("deploy(bytes32,bytes32,bytes)"))
always has the same answer so it can be pre calculated and only the answer should be given to the immutable or constant so it doesnt get calculated every time the contract is made.
saves 97 gas each time
instead of highWaterMark
use the cached version highWaterMark_
in these lines. 2 instances
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
vault.sol has lots of external and public function and lots of properties and it would be much more efficient if the functions and properties been sorted by the most used functions and properties.
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
4 instances here
You can cut out 10 opcodes in the creation-time EVM bytecode if you declare a constructor payable. Making the constructor payable eliminates the need for an initial check of msg.value == 0 and saves 13 gas on deployment with no security risks.
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.
the keccak256() used here will always be the same so there is no need to calculate it inside the function. just pre calculate the answer to it and use the answer inside the code instead. use a comment to show what it was to the readers
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256("1"),
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256("1"),
#0 - c4-judge
2023-03-01T00:01:39Z
dmvt marked the issue as grade-a