Platform: Code4rena
Start Date: 24/03/2023
Pot Size: $49,200 USDC
Total HM: 20
Participants: 246
Period: 6 days
Judge: Picodes
Total Solo HM: 1
Id: 226
League: ETH
Rank: 208/246
Findings: 1
Award: $10.79
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Rolezn
Also found by: 0x3b, 0xGordita, 0xSmartContract, 0xhacksmithh, 0xnev, 0xpanicError, 4lulz, Angry_Mustache_Man, ArbitraryExecution, Aymen0909, Bason, BlueAlder, EvanW, Franfran, HHK, Haipls, IgorZuk, JCN, KrisApostolov, Madalad, MiksuJak, MiniGlome, RaymondFam, ReyAdmirado, Rickard, Sathish9098, Udsen, adriro, alexzoid, anodaram, arialblack14, c3phas, carlitox477, ch0bu, chaduke, codeslide, d3e4, dicethedev, ernestognw, fatherOfBlocks, georgits, hunter_w3b, inmarelibero, lukris02, mahdirostami, maxper, pavankv, pixpi, rotcivegaf, smaul, tank, tnevler, wen, yac
10.7864 USDC - $10.79
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.
derivatives[i].balance()
is being read twice for each iteration. cache it at the start of the for loop to reduce a complex SLOAD per iteration.
derivativeCount
is being read twice in #L71 and #L84 so it can be cached before #L71 to reduce one SLOAD
same for derivativeCount
here in #L140 and #L147
totalWeight
is being read every time in the loop but it is not changed in the loop so it is always the same while the loop is going. cache it before the loop to stop SLOADs on every iteration and only do one before the loop.
same for totalWeight
here in #L150
cache derivatives[i].balance()
here because the if statement is usually true and inside it will be another complex SLOAD for it. even if the if statement be false we only risk losing 3 gas because of caching it and it is totally worth it. cache derivatives[i].balance()
before the if statement. this will save lots of gas because it saves gas for every iteration of for loop
weights[i]
can be cached to possibly reduce a complex SLOAD. it is read in both #L148 and #L149 and usually #L149 will happen so it is worth to risk losing 3 gas for caching to save huge amount of gas. saves gas for every iteration of for loop
derivativeCount
is being read 4 times and it should be cached at the start of the function to reduce 3 extra SLOADs. with caching it we can stop 2 reads in #L186 and #L187 but for #L191 and #194 we can use derivativeCount + 1
or we can update the cached version as well as the statevar near #L188
make the variable outside the loop and only give the value to variable inside
weight
ethAmount
depositAmount
derivativeReceivedEthValue
derivativeAmount
There is a chance that the first part will be true so the second part doesn’t need to be checked, so it is better to use the part that we have first instead of the part that needs to be called.
if (weights[i] == 0 || ethAmountToRebalance == 0)
should be if (ethAmountToRebalance == 0 || weights[i] == 0)
for a possible reduce of a complex SLOAD
bool
for storage incurs overheadBooleans 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
pauseStaking
and pauseUnstaking
are inherited and both bool variables consider changing them because it has impact on current contracts in scope but the contract they are declared in is out of scope
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.
name()
ethPerDerivative()
balance()
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 _minAmount
instead
use _maxAmount
instead
use _pause
instead
use _pause
instead
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
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
these parts of code will use operations to calculate every time they are used, so instead just pre calculate them and use them as constants made in the contract
1 * 10 ** 16
and 10 ** 18
and 96 * 2
and 5 * 10 ** 17
and 200 * 10 ** 18
#0 - c4-sponsor
2023-04-10T19:43:03Z
toshiSat marked the issue as sponsor acknowledged
#1 - c4-judge
2023-04-23T19:06:44Z
Picodes marked the issue as grade-b