Platform: Code4rena
Start Date: 09/12/2022
Pot Size: $90,500 USDC
Total HM: 35
Participants: 84
Period: 7 days
Judge: GalloDaSballo
Total Solo HM: 12
Id: 192
League: ETH
Rank: 23/84
Findings: 1
Award: $662.69
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: Aymen0909, Deekshith99, Faith, JC, ReyAdmirado, c3phas
662.6879 USDC - $662.69
Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmaccess (100 gas) with a PUSH32 (3 gas).
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
move isClosed
near provider
so they can be in one slot and the struct uses one less slot
move expired
near owner or asset
so they can be in one slot and the struct uses one less slot
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.
chainlinkEnabled
and paused
should be declared near trading
so they will be in one slot instead of 3 slots
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.
longOi[_trade.asset][_trade.tigAsset]
will be used twice at line 48 and 49 if the if statement passes so should cache it inside the if statement of line 47
shortOi[_trade.asset][_trade.tigAsset]
will be used twice at line 55 and 56 if the if statement passes so should cache it inside the if statement of line 54
longOi[_asset][_tigAsset]
will be checked both in if statement of line 100 and 106 so its a good idea to cache the value before the if statement of line 100 which will possibly have 2 less storage read if the L100 condition passes and 1 less storage read if the L106 condition passes
shortOi[_asset][_tigAsset]
will be checked both in if statement of line 100 and 106 so its a good idea to cache the value before the if statement of line 100 which will possibly have 1 less storage read if the L100 condition passes and 2 less storage read if the L106 condition passes
caching lastUpdate[_asset][_tigAsset]
will save lots of gas at a low risk of losing small gas for a stack var (both if statements of L101 and L108 should fail to lose the small amount of gas). cache it before L100
caching fundingDeltaPerSec[_asset][_tigAsset]
will save lots of gas at a low risk of losing small gas for a stack var (both if statements of L101 and L108 should fail to lose the small amount of gas). cache it before L100
cache bondPaid[_id][bond.asset]
before this line
highly possible gas save with caching totalShares[bond.asset]
for a small risk of losing a small amount of gas.
cache before the if statement
epoch[bond.asset]
cache before this line
make a stack var as a place holder for assets[i]
before the for loop and put the assets[i]
inside that place holder inside the for loop. saves lotta gas because its a for loop
cache userPaid[owner][assets[i]]/balanceOf(owner)
whole for a big gas save because its inside the loop
cache userPaid[from][assets[i]]/balanceOf(from)
whole for a big gas save because its inside the loop
cache accRewardsPerNFT[assets[i]]
whole for a big gas save because its inside the loop
cache tokenIndex[_token]
read in line 91 and 92 can be read once
cache tokens[tokens.length-1]
read in line 91 and 92 can be read once
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
++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-loopsIn Solidity 0.8+, there’s a default overflow check on unsigned integers. It’s possible to uncheck this in for-loops and save some gas at each iteration, but at the cost of some code readability, as this uncheck cannot be made inline.
Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas
&&
saves gasthis will have a large deployment gas cost but with enough runtime calls the split require version will be 3 gas cheaper
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.
require in L106 in particular is more expensive than the rest so put it as the last require check
swap 110 and 111
329 should be the last require out of the 3 requires
swap 66 and 65
or operator
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 instead of the part that needs to be called.
use one specific solidity version
Use a solidity version of at least 0.8.0 to get overflow protection without SafeMath
Use a solidity version of at least 0.8.2 to get compiler automatic inlining
Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads
Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings
Use a solidity version of at least 0.8.10 to have external
calls skip contract existence checks if the external call has a return value
Use a solidity version of at least 0.8.12 to get string.concat() to be used instead of abi.encodePacked(<str>,<str>)
Use a solidity version of at least 0.8.13 to get the ability to use using for
with a list of free functions
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
Changing from public will remove the compiler-introduced checks for msg.value and decrease the contract’s method ID table size
liqPrice
This will save near 97 gas
use _newMargin
instead of _trade.margin
in
before transfer we should check for amount being 0 so the function doesnt run when its not gonna do anything
_margin/_marginDecMultiplier
shouldnt be 0
amount
instead of _trade.asset
use _asset
cache the value of assetsLength()
before to save gas
#0 - GalloDaSballo
2022-12-27T17:20:36Z
2.1K + 7 = 14.7k
2k each (saves a Cold SLOAD for end users)
Rest is not as impactful (may count for tie breakers)
16700
#1 - c4-sponsor
2023-01-09T18:15:47Z
GainsGoblin marked the issue as sponsor confirmed
#2 - GalloDaSballo
2023-01-23T07:55:33Z
Rest is not as impactful, let's say 300 gas
#3 - GalloDaSballo
2023-01-23T07:55:36Z
17000
#4 - c4-judge
2023-01-23T08:05:03Z
GalloDaSballo marked the issue as grade-a