Caviar Private Pools - ReyAdmirado's results

A fully on-chain NFT AMM that allows you to trade every NFT in a collection.

General Information

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

Caviar

Findings Distribution

Researcher Performance

Rank: 45/120

Findings: 1

Award: $98.99

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: JCN

Also found by: ReyAdmirado, Sathish9098, adriro, hunter_w3b, matrix_0wl

Labels

bug
G (Gas Optimization)
grade-b
G-05

Awards

98.9907 USDC - $98.99

External Links

1. Structs can be packed into fewer storage slots

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

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

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

3. Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if statement

require(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

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. can make the variable outside the loop to save gas

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

6. usage of uint/int smaller than 32 bytes (256 bits) incurs overhead

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

7. Ternary over if ... else

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

8. public functions not called by the contract should be declared external instead

Contracts are allowed to override their parents’ functions and change the visibility from external to public and can save gas by doing so.

9. Use assembly to check for address(0)

saves 6 gas per instance

17 instances total in - PrivatePool.sol

10. Functions guaranteed to revert when called by normal users can be marked 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.

11. Optimize names to save gas

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

12. Setting the constructor to payable

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.

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

18 - 4

keccak256("ERC3156FlashBorrower.onFlashLoan")

14. Use selfbalance() instead of address(this).balance

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

15. Duplicated require()/revert() checks should be refactored to a modifier or function

Saves deployment costs

        if (block.timestamp > deadline && deadline != 0) {
            revert DeadlinePassed();
        }
    baseToken != address(0)

16. sorting the lines better will result in less storage access needed which result in gas save

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

17. cache parts of code for second use

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

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