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
Rank: 29/59
Findings: 2
Award: $113.89
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: rbserver
Also found by: 0x1f8b, 0xAgro, 0xGusMcCrae, 0xSmartContract, Awesome, Breeje, DadeKuma, Diana, IllIllI, Josiah, Moksha, RaymondFam, Rolezn, SaeedAlipoor01988, Udsen, Viktor_Cortess, brgltd, btk, chaduke, cryptonue, ddimitrov22, delfin454000, descharre, fatherOfBlocks, georgits, hansfriese, lukris02, luxartvinsec, martin, matrix_0wl, mookimgo, oberon, popular00, shark, tnevler
65.3481 USDC - $65.35
ID | Finding | Instances |
---|---|---|
1 | Checks for enums are unnecessary | 3 |
2 | Remove libraries that are never used | 1 |
3 | Functions should be grouped according to their visibility and ordered | - |
4 | Unspecific compiler version pragma | 4 |
5 | Miscellaneous | 1 |
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
According to the solidity style guide functions should be ordered like the following:
For example in TimeswapV2Option.sol, constructor is placed after a function.
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:
#0 - c4-judge
2023-02-02T11:41:30Z
Picodes marked the issue as grade-b
🌟 Selected for report: 0xSmartContract
Also found by: 0x1f8b, 0xackermann, Aymen0909, Beepidibop, IllIllI, Iurii3, Rageur, RaymondFam, ReyAdmirado, Rolezn, SaeedAlipoor01988, Udsen, Viktor_Cortess, W0RR1O, W_Max, atharvasama, c3phas, chaduke, descharre, fatherOfBlocks, kaden, matrix_0wl, shark
48.5424 USDC - $48.54
ID | Finding | Gas saved | Instances |
---|---|---|---|
1 | Unneccesary checks | - | 1 |
2 | Add unchecked to increments or decrements that can't under/overflow | 217 | 7 |
3 | Redundant checks | - | 6 |
4 | Call block.timestamp directly in an if statement | - | 3 |
5 | Use double if statements instead of && | 60 | 7 |
6 | Remove return variable when it's never accessed | 30 | 1 |
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.
PositionLibrary.check(position);
function check(TimeswapV2OptionPosition position) internal pure { if (uint256(position) >= 3) revert InvalidPosition(); }
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.
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:
mint()
34 gas saved.Id increments always by 1. TimeswapV2Token mint()
183 gas saved
+ 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);
transferLiquidity()
function in the TimeswapV2Pool contract.burn()
function in the TimeswapV2Pool contract.deleverage()
function in the TimeswapV2Pool contract.leverage()
function in the TimeswapV2Pool contract.rebalance()
function in the TimeswapV2Pool contract.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); }
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:
Return variable optionPair is never accessed when getWithCheck function is called. Removing the return variables saves around 15 gas for burn()
and mint()
.
- 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