Platform: Code4rena
Start Date: 07/04/2023
Pot Size: $47,000 USDC
Total HM: 20
Participants: 120
Period: 6 days
Judge: GalloDaSballo
Total Solo HM: 4
Id: 230
League: ETH
Rank: 45/120
Findings: 1
Award: $98.99
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: JCN
Also found by: ReyAdmirado, Sathish9098, adriro, hunter_w3b, matrix_0wl
98.9907 USDC - $98.99
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
struct Buy
can be while using one less slot if isPublicPool
which is bool is made near nft
which is address they can be packed into one slot instead of 2 slots
same thing as before for struct Sell
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.
cache baseToken
before #L225 and if code reverts in that line we will just lose 3 gas for it but if it doesnt, worst case scenario we will save ~100 gas 2 of the 5 extra uses are inside a loop and gonna be read again per iteration so this will be huge save.
cache nft
and payRoyalties
before #L238. 100 gas is being read for every iteration of for loop although nft
and payRoyalties
will not change, so cache them before the loop and used the cached versions inside.
same thing for nft
and payRoyalties
here again but this time cache nft
before #L316 because there is a possibilty for a 100 more gas save there
baseToken
can be cached here for possibly a lot of gas save because its used in a loop and it is being read again later with some conditions, although it is very possible that its gonna save gas if its cached before #L328
cache baseToken
before #L397 for at least 97 gas save if the revert in #L397 doesnt happen and possibly ~300 gas save if conditions #L421 and #L426 work.
cache nft
because its being read every iteration in 2 for loops although its not being changed. cache it before #L400 though because there is a possibilty for ~100 gas save there too.
baseToken
is at least being read twice here or best case 4 times. caching it before #L489 at least saves 97 gas and possibly ~300.
nft
is being read every iteration of the loop here so cache it before the loop to save gas.
cache baseToken
before #L635 for possible gas ~200 gas save if function doesnt revert in #L635.
cache baseToken
before #L733. this will risk losing 3 gas if baseToken == address(0)
is true but otherwise it will save 97 gas.
same thing for #L744
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
(netInputAmount - feeAmount - protocolFeeAmount) / tokenIds.length;
can be done inside a unchecked bracket because its happened before so it cant underflow
msg.value - netInputAmount
can be in unchecked because its being checked in the same line
msg.value - feeAmount - protocolFeeAmount
can be unchecked because its checked before to not underflow
18 - 4
can be unchecked
36 - ERC20(baseToken).decimals()
can be unchecked because it cant underflow
<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesUsing the addition operator instead of plus-equals saves gas (13 or 113 each dependant on the usage see here)
make the variable outside the loop and only give the value to variable inside
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
many operations are being done on these kinds in - PrivatePool.sol
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.
saves 6 gas per instance
Factory.sol#L87 2 instances here
17 instances total in - PrivatePool.sol
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
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.
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.
18 - 4
keccak256("ERC3156FlashBorrower.onFlashLoan")
Use assembly when getting a contract's balance of ETH.
You can use selfbalance() instead of address(this).balance when getting your contract's balance of ETH to save gas. Additionally, you can use balance(address) instead of address.balance() when getting an external contract's balance of ETH.
Saves 15 gas when checking internal balance, 6 for external
Saves deployment costs
if (block.timestamp > deadline && deadline != 0) { revert DeadlinePassed(); }
baseToken != address(0)
of 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
change this
baseToken = _baseToken; nft = _nft; virtualBaseTokenReserves = _virtualBaseTokenReserves; virtualNftReserves = _virtualNftReserves; changeFee = _changeFee; feeRate = _feeRate; merkleRoot = _merkleRoot; useStolenNftOracle = _useStolenNftOracle; payRoyalties = _payRoyalties; // mark the pool as initialized initialized = true;
to this
merkleRoot = _merkleRoot; virtualBaseTokenReserves = _virtualBaseTokenReserves; virtualNftReserves = _virtualNftReserves; baseToken = _baseToken; nft = _nft; changeFee = _changeFee; feeRate = _feeRate; // mark the pool as initialized initialized = true; payRoyalties = _payRoyalties; useStolenNftOracle = _useStolenNftOracle;
(although code looks cleaner if the sorting of declarations changes and initialized
be the last bool to be made, consider changing it here - PrivatePool.sol#L94-L100)
link to the code
instead of doing some operations again answer can be cached at the first time and be used every where.
cache (netInputAmount - feeAmount - protocolFeeAmount)
before #L230 and use the cached version in #L236 as well
#0 - GalloDaSballo
2023-04-28T12:49:56Z
1 Structs can be packed into fewer storage slots Not Storage
2 state variables should be cached in stack variables rather than re-reading them from storage L
3 Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if statement NC
4 <x> += <y> costs more gas than <x> = <x> + <y> for state variables NC
5 can make the variable outside the loop to save gas NC
6 usage of uint/int smaller than 32 bytes (256 bits) incurs overhead Ignoring
7 Ternary over if ... else Ignoring
8 public functions not called by the contract should be declared external instead Ignoring
9 Use assembly to check for address(0) NC
10 Functions guaranteed to revert when called by normal users can be marked payable Ignoring
11 Optimize names to save gas Ignoring
12 Setting the constructor to payable Ignoring
13 part of the code can be pre calculated R
14 Use selfbalance() instead of address(this).balance Ignoring
15 Duplicated require()/revert() checks should be refactored to a modifier or function Ignoring
16 sorting the lines better will result in less storage access needed which result in gas save Ignoring
17 cache parts of code for second use NC
#1 - GalloDaSballo
2023-04-28T12:51:13Z
1L 1R 5NC
#2 - c4-judge
2023-05-01T09:25:51Z
GalloDaSballo marked the issue as grade-b