Tigris Trade contest - ReyAdmirado's results

A multi-chain decentralized leveraged exchange featuring instant settlement and guaranteed price execution on 30+ pairs.

General Information

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

Tigris Trade

Findings Distribution

Researcher Performance

Rank: 23/84

Findings: 1

Award: $662.69

Gas:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: IllIllI

Also found by: Aymen0909, Deekshith99, Faith, JC, ReyAdmirado, c3phas

Labels

bug
G (Gas Optimization)
grade-a
sponsor confirmed
G-01

Awards

662.6879 USDC - $662.69

External Links

1. State variables only set in the constructor should be declared immutable.

Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmaccess (100 gas) with a PUSH32 (3 gas).

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

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

3. state variables can be packed into fewer storage slots

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

4. state variables should be cached in stack variables rather than re-reading them from storage (the ones that wasnt found)

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

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

6. <x> += <y> costs more gas than <x> = <x> + <y> for state variables

Using the addition operator instead of plus-equals saves gas

7. not using the named return variables when a function returns, wastes deployment gas

8. can make the variable outside the loop to save gas

9. ++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-loops

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

10. require()/revert() strings longer than 32 bytes cost extra gas

Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas

11. splitting require() statements that use && saves gas

this will have a large deployment gas cost but with enough runtime calls the split require version will be 3 gas cheaper

12. require() or revert() statements that check input arguments should be at the top of the function

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

13. swap positions of two side of 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.

14. use a more recent version of solidity

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

15. abi.encode() is less efficient than abi.encodepacked()

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

17. public library function should be made private/internal

Changing from public will remove the compiler-introduced checks for msg.value and decrease the contract’s method ID table size

liqPrice

18. should use arguments instead of state variable

This will save near 97 gas

use _newMargin instead of _trade.margin in

19. before some functions we should check some variables for possible gas save

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

20. use existing cached version of state var

instead of _trade.asset use _asset

21. dont use a function to get the length value inside to for statement just cache it before the loop

cache the value of assetsLength() before to save gas

#0 - GalloDaSballo

2022-12-27T17:20:36Z

1. State variables only set in the constructor should be declared immutable.

2.1K + 7 = 14.7k

2. Structs can be packed into fewer storage slots

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

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