Timeswap contest - ReyAdmirado's results

Like Uniswap, but for lending & borrowing.

General Information

Platform: Code4rena

Start Date: 20/01/2023

Pot Size: $90,500 USDC

Total HM: 10

Participants: 59

Period: 7 days

Judge: Picodes

Total Solo HM: 4

Id: 206

League: ETH

Timeswap

Findings Distribution

Researcher Performance

Rank: 53/59

Findings: 1

Award: $48.54

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

48.5424 USDC - $48.54

Labels

bug
G (Gas Optimization)
grade-b
edited-by-warden
G-15

External Links

1. Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate

Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key’s keccak256 hash (Gkeccak256 - 30 gas) and that calculation’s associated stack operations.

_timeswapV2TokenPositions and _timeswapV2TokenPositionIds are used in the same function a bunch of times

2. state variables should be cached in stack variables rather than re-reading them from storage (ones that were not mentioned in c4udit)

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.

liquidityPosition.long0Fees is being read once inside the if check and if it passes its gonna be read again inside the if statement.if the condition fails its gonna be read inside the else statement instead. so we can cache the liquidityPosition.long0Fees variable before the if condition so it can be read one less time.

liquidityPosition.long1Fees is being read once inside the if check and if it passes its gonna be read again inside the if statement.if the condition fails its gonna be read inside the else statement instead. so we can cache the liquidityPosition.long1Fees variable before the if condition so it can be read one less time.

liquidityPosition.shortFees is being read once inside the if check and if it passes its gonna be read again inside the if statement.if the condition fails its gonna be read inside the else statement instead. so we can cache the liquidityPosition.shortFees variable before the if condition so it can be read one less time.

pool.liquidity is being read twice. cache it at start of the function to save one extra read from storage

pool.sqrtInterestRate is being read twice. cache it at start of the function to save one extra read from storage

because usually the if statement will be true we can cache pool.liquidity and save lots of gas with a low risk of using only 3 more gas

pool.long0ProtocolFees is being read once inside the if check and if it passes its gonna be read again inside the if statement.if the condition fails its gonna be read inside the else statement instead. so we can cache the pool.long0ProtocolFees variable before the if condition so it can be read one less time.

pool.long1ProtocolFees is being read once inside the if check and if it passes its gonna be read again inside the if statement.if the condition fails its gonna be read inside the else statement instead. so we can cache the pool.long1ProtocolFees variable before the if condition so it can be read one less time.

pool.shortProtocolFees is being read once inside the if check and if it passes its gonna be read again inside the if statement.if the condition fails its gonna be read inside the else statement instead. so we can cache the pool.shortProtocolFees variable before the if condition so it can be read one less time.

pool.liquidity is read twice in both if checks at lines L271 and L279

pool.liquidity is being read three times. cache it at the start of the function. read once in L559 once in one of the if or else statements below that and once in L530. cache it to save 2 extra storage read

pool.liquidity is being read twice and highly possibly 4 times cache it at the start of the function to save lots of gas.read once in L559 once in one of the if or else statements below that and highly possibly gonna be read in L639 and L653

pool.liquidity is being read twice. L666 and once of the L697 or L724

option.totalLong0 is being read twice. firstly in L192 and in one of the if or elses below it. cache it to have one less storage read(also theres is -= which is wasting gas and is pointed out at another point of the report. after changing that there is gonna be one more storage read that can be prevented)

option.totalLong1 is being read twice. firstly in L192 and in one of the if or elses below it. cache it to have one less storage read.(also theres is -= which is wasting gas and is pointed out at another point of the report. after changing that there is gonna be one more storage read that can be prevented)

_feesPositions[id][msg.sender] is being read twice. cache it before L199 and use the functions on the cached version of it on L199 and L216

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

Using the addition operator instead of plus-equals saves gas

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

the variables declared are never used so there is no need of declaring them

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. use a more recent version of solidity

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

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

8. switch if check positions

the if check used on the first checks uses higher gas, so we can swap the positions and sort them by gas usage so if a cheaper one will return error for us we dont have to check the other expensive ones

switch the position of the first if to last because its the most expensive

bring the first to last because it uses a function

bring the first to last because it uses a function

#0 - c4-judge

2023-02-02T12:28:38Z

Picodes 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