Timeswap contest - descharre'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: 29/59

Findings: 2

Award: $113.89

QA:
grade-b
Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

65.3481 USDC - $65.35

Labels

bug
grade-b
QA (Quality Assurance)
edited-by-warden
Q-33

External Links

Summary

Non critical

IDFindingInstances
1Checks for enums are unnecessary3
2Remove libraries that are never used1
3Functions should be grouped according to their visibility and ordered-
4Unspecific compiler version pragma4
5Miscellaneous1

Details

Non critical

1 Checks for enums are unnecessary

The checks in the enum libraries to check if the value of the enum exists are unnecessary. It's not possible to input an enum value that does not exist. EVM will revert if you try that

2 Remove libraries that are never used

Duration.sol

3 Functions should be grouped according to their visibility and ordered

According to the solidity style guide functions should be ordered like the following:

  • constructor
  • receive function (if exists)
  • fallback function (if exists)
  • external
  • public
  • internal
  • private

For example in TimeswapV2Option.sol, constructor is placed after a function.

4 Unspecific compiler version pragma

Avoid floating pragmas for non-library contracts. If the pragma version is unspecific, a know vulnerable compiler version may accidently be selected when deploying. Used in:

Miscellaneous

Array is never updated but length is read

TimeswapV2PoolFactory.sol#L33

#0 - c4-judge

2023-02-02T11:41:30Z

Picodes marked the issue as grade-b

Awards

48.5424 USDC - $48.54

Labels

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

External Links

Summary

IDFindingGas savedInstances
1Unneccesary checks-1
2Add unchecked to increments or decrements that can't under/overflow2177
3Redundant checks-6
4Call block.timestamp directly in an if statement-3
5Use double if statements instead of &&607
6Remove return variable when it's never accessed301

Details

1 Unneccesary checks

Check if an enum exist is unneccesary, EVM will revert if you try to input an enum value does not exist. Saves some gas because you don't need the check.

TimeswapV2Option.sol#L101

    PositionLibrary.check(position);

Position.sol#L22-L24

    function check(TimeswapV2OptionPosition position) internal pure {
        if (uint256(position) >= 3) revert InvalidPosition();
    }

2 Add unchecked to increments or decrements that can't under/overflow

If amount is really large, the evm will do an underflow check on the decrement of msg.sender and then revert. So amount can never be large enough to overflow when you increment the to address.

Option.sol#L60-L71

    function transferPosition(Option storage option, address to, TimeswapV2OptionPosition position, uint256 amount) internal {
        if (position == TimeswapV2OptionPosition.Long0) {
            option.long0[msg.sender] -= amount;
+           unchecked{
            option.long0[to] += amount;
+           }
        } else if (position == TimeswapV2OptionPosition.Long1) {
-           option.long1[to] += amount;
            option.long1[msg.sender] -= amount;
+           unchecked{
+           option.long1[to] += amount;
+           }
        } else if (position == TimeswapV2OptionPosition.Short) {
            option.short[msg.sender] -= amount;
+           unchecked{
            option.short[to] += amount;
+           }
        }
    }

Same applies to:

  • Option.sol#L175: if amount is too large, increment above will revert because totalLong is always more than long of 1 user.
  • Option.sol#L179: if amount is too large, increment above will revert
  • TimeswapV2LiquidityToken.sol#L114: increments with 1 and id is a uint256 so there is no possibility of overflowing. TimeswapV2LiquidityToken mint() 34 gas saved.

Id increments always by 1. TimeswapV2Token mint() 183 gas saved

3 Redundant checks

+       LiquidityPosition storage liquidityPosition = pool.liquidityPositions[msg.sender];
        if (pool.liquidity != 0) {
            if (maturity > blockTimestamp) updateDurationWeightBeforeMaturity(pool, blockTimestamp);
            else if (pool.lastTimestamp < maturity) updateDurationWeightAfterMaturity(pool, maturity, blockTimestamp);
+           liquidityPosition.update(pool.long0FeeGrowth, pool.long1FeeGrowth, pool.shortFeeGrowth);
        }

        // Update the fee growth and fees of caller.
-       LiquidityPosition storage liquidityPosition = pool.liquidityPositions[msg.sender];

-       if (pool.liquidity != 0) liquidityPosition.update(pool.long0FeeGrowth, pool.long1FeeGrowth, pool.shortFeeGrowth);

        (long0Amount, long1Amount, shortAmount) = liquidityPosition.collectTransactionFees(long0Requested, long1Requested, shortRequested);

4 Call block.timestamp directly in an if statement

When you compare the block timestamp of the current block to something. It's cheaper to use block.timestamp directly than calling the blockTimeStamp function. In the following lines it's also not necessary to cast block.timestamp to uint96.

On other places like error handling it's also not necessary to cast block.timestamp to a uint96.

    /// @dev Reverts when the maturity is already matured.
    /// @param maturity The maturity.
    /// @param blockTimestamp The current block timestamp.
    function alreadyMatured(uint256 maturity, uint96 blockTimestamp) internal pure {
        revert AlreadyMatured(maturity, blockTimestamp);
    }

    /// @dev Reverts when the maturity is still active.
    /// @param maturity The maturity.
    /// @param blockTimestamp The current block timestamp.
    function stillActive(uint256 maturity, uint96 blockTimestamp) internal pure {
        revert StillActive(maturity, blockTimestamp);
    }

5 Use double if statements instead of &&

Using multiple if statements instead of && is cheaper when the statement fails. However there will be more deployment costs but this will only be one time.

In the following example, if the first statement fails it will save about 60 gas, second statement fails will save about 40 gas. And if the last statement fails it will save 20 gas. TimeswapV2Pool.sol#L167

-    if (long0Fees == 0 && long1Fees == 0 && shortFees == 0) Error.zeroInput();
+    if (long0Fees == 0) Error.zeroInput();
+    if (long1Fees == 0) Error.zeroInput();
+    if (shortFees == 0) Error.zeroInput();

Gas optimization can also be done at:

6 Remove return variable when it's never accessed

Return variable optionPair is never accessed when getWithCheck function is called. Removing the return variables saves around 15 gas for burn() and mint().

PoolFactory.sol#L43-L49

-   function getWithCheck(address optionFactory, address poolFactory, address token0, address token1) internal view returns (address optionPair, address poolPair) {
+   function getWithCheck(address optionFactory, address poolFactory, address token0, address token1) internal view returns (address poolPair) {
-       optionPair = optionFactory.getWithCheck(token0, token1);
+       address optionPair = optionFactory.getWithCheck(token0, token1);
        poolPair = ITimeswapV2PoolFactory(poolFactory).get(optionPair);

        if (poolPair == address(0)) Error.zeroAddress();
    }

TimeswapV2LiquidityToken.sol#L157

-       (, address poolPair) = PoolFactoryLibrary.getWithCheck(optionFactory, poolFactory, param.token0, param.token1);
+       (address poolPair) = PoolFactoryLibrary.getWithCheck(optionFactory, poolFactory, param.token0, param.token1);

Is also called at

#0 - c4-judge

2023-02-02T12:30:57Z

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