Platform: Code4rena
Start Date: 26/09/2022
Pot Size: $50,000 USDC
Total HM: 13
Participants: 113
Period: 5 days
Judge: 0xean
Total Solo HM: 6
Id: 166
League: ETH
Rank: 33/113
Findings: 2
Award: $85.91
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xNazgul
Also found by: 0x1f8b, 0x52, 0xDecorativePineapple, 0xSmartContract, 0xmatt, Aeros, Aymen0909, Bnke0x0, Chom, CodingNameKiki, Deivitto, DimitarDimitrov, IllIllI, JC, Jeiwan, Lambda, Matin, Migue, Mukund, Ocean_Sky, Olivierdem, RaymondFam, RockingMiles, Rolezn, Ruhum, Satyam_Sharma, Shinchan, Tomo, Trabajo_de_mates, V_B, Waze, __141345__, a12jmx, ajtra, asutorufos, aysha, brgltd, bulej93, carrotsmuggler, catchup, cccz, chrisdior4, cryptonue, cryptphi, d3e4, defsec, delfin454000, durianSausage, erictee, fatherOfBlocks, gogo, kaden, karanctf, ladboy233, lukris02, mahdikarimi, martin, mics, natzuu, oyc_109, p_crypt0, pedr02b2, rbserver, reassor, rotcivegaf, rvierdiiev, sikorico, slowmoses, sorrynotsorry, tnevler, trustindistrust
61.8916 USDC - $61.89
The existence of a named return variable that is never used (here, mostBitPos
) is potentially perplexing
function getMostSignificantBit(uint256 word) internal pure returns (uint8 mostBitPos) {
@param
statementPriceMovementMath.sol: L125-143
/// @notice Computes the result of swapping some amount in, or amount out, given the parameters of the swap /// @dev The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive /// @param currentPrice The current Q64.96 sqrt price of the pool /// @param targetPrice The Q64.96 sqrt price that cannot be exceeded, from which the direction of the swap is inferred /// @param liquidity The usable liquidity /// @param amountAvailable How much input or output amount is remaining to be swapped in/out /// @param fee The fee taken from the input amount, expressed in hundredths of a bip /// @return resultPrice The Q64.96 sqrt price after swapping the amount in/out, not to exceed the price target /// @return input The amount to be swapped in, of either token0 or token1, based on the direction of the swap /// @return output The amount to be received, of either token0 or token1, based on the direction of the swap /// @return feeAmount The amount of input that will be taken as a fee function movePriceTowardsTarget( bool zeroToOne, uint160 currentPrice, uint160 targetPrice, uint128 liquidity, int256 amountAvailable, uint16 fee )
@param
statement missing for zeroToOne
The same typo (an
) occurs in all four lines referenced below. I normally wouldn't flag "an" versus "a", but an timestamp
comes across as awkward and should be corrected.
Example (DataStorage.sol: L193):
/// @dev Reverts if an timepoint at or before the desired timepoint timestamp does not exist.
Change an
to a
in each case
bool initialized; // these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks
Change sstores
to stores
In theory, comments over 79 characters should wrap using multi-line comment syntax. Even if somewhat longer comments are acceptable and a scroll bar is provided, very long comments can interfere with readability. Below are five examples of code with lines that could benefit from wrapping, as shown:
// if liquidityDelta is negative and the tick was toggled, it means that it should not be initialized anymore, so we delete it
Suggestion:
// If liquidityDelta is negative and the tick was toggled, it means that // it should not be initialized anymore, so we delete it.
/// @notice Initialize the dataStorage array by writing the first slot. Called once for the lifecycle of the timepoints array
Suggestion:
/// @notice Initialize the dataStorage array by writing the first slot. /// Called once for the lifecycle of the timepoints array.
/// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos` /// @dev Reverts if `secondsAgos` > oldest timepoint /// @param self The stored dataStorage array /// @param time The current block.timestamp /// @param secondsAgos Each amount of time to look back, in seconds, at which point to return an timepoint /// @param tick The current tick /// @param index The index of the timepoint that was most recently written to the timepoints array /// @param liquidity The current in-range pool liquidity /// @return tickCumulatives The tick * time elapsed since the pool was first initialized, as of each `secondsAgo` /// @return secondsPerLiquidityCumulatives The cumulative seconds / max(1, liquidity) since the pool was first initialized, as of each `secondsAgo` /// @return volatilityCumulatives The cumulative volatility values since the pool was first initialized, as of each `secondsAgo` /// @return volumePerAvgLiquiditys The cumulative volume per liquidity values since the pool was first initialized, as of each `secondsAgo`
Suggestion:
/// @notice Returns the accumulator values as of each time seconds ago /// from the given time in the array of `secondsAgos`. /// @dev Reverts if `secondsAgos` > oldest timepoint /// @param self The stored dataStorage array /// @param time The current block.timestamp /// @param secondsAgos Each amount of time to look back, in seconds, /// at which point to return an timepoint. /// @param tick The current tick /// @param index The index of the timepoint that was most recently /// written to the timepoints array. /// @param liquidity The current in-range pool liquidity /// @return tickCumulatives The tick * time elapsed since the pool was /// first initialized, as of each `secondsAgo`. /// @return secondsPerLiquidityCumulatives The cumulative seconds / max(1, liquidity) /// since the pool was first initialized, as of each `secondsAgo`. /// @return volatilityCumulatives The cumulative volatility values since the /// pool was first initialized, as of each `secondsAgo`. /// @return volumePerAvgLiquiditys The cumulative volume per liquidity values /// since the pool was first initialized, as of each `secondsAgo`.
/// @dev The mapping uses int16 for keys since ticks are represented as int24 and there are 256 (2^8) values per word.
Suggestion:
/// @dev The mapping uses int16 for keys since ticks are represented as int24 /// and there are 256 (2^8) values per word
/// @return token0Delta Amount of token0 required to cover a position of size liquidity between the two passed prices
Suggestion:
/// @return token0Delta Amount of token0 required to cover a position of /// size liquidity between the two passed prices.
require
revert stringA require
message should be included to enable users to understand the reason for failure
require(msg.sender == owner);
Recommendation: Add a brief (<= 32 char) explanatory message.
Similarly for each of the 26 requires
referenced below. Also, consider using custom error codes (which would be cheaper than revert strings).
Require
message is inadequateA revert string should provide enough information for users to understand the reason for failure
require(topTick < TickMath.MAX_TICK + 1, 'TUM');
Recommendation: Add a brief (<= 32 char) explanatory message instead of using an acronym that not all users may understand.
Similarly for each of the 23 requires
referenced below. Again, consider using custom error codes (which would be cheaper than revert strings).
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x5rings, 0xNazgul, 0xRoxas, 0xSmartContract, 0xbepresent, 0xmatt, Aeros, Amithuddar, Awesome, Aymen0909, B2, Bnke0x0, ChristianKuri, CodingNameKiki, Deivitto, Diraco, Fitraldys, HardlyCodeMan, JC, Mukund, Noah3o6, Olivierdem, RaymondFam, ReyAdmirado, RockingMiles, Rolezn, Ruhum, Saintcode_, Shinchan, SnowMan, TomJ, Tomio, Tomo, V_B, Waze, __141345__, ajtra, asutorufos, aysha, beardofginger, bobirichman, brgltd, bulej93, c3phas, ch0bu, cryptonue, defsec, delfin454000, dharma09, durianSausage, emrekocak, erictee, fatherOfBlocks, francoHacker, gianganhnguyen, gogo, imare, kaden, karanctf, ladboy233, lukris02, m_Rassska, martin, medikko, mics, natzuu, oyc_109, peiw, rbserver, ret2basic, rotcivegaf, saian, shark, slowmoses, tnevler, trustindistrust, zeesaw, zishansami
24.0182 USDC - $24.02
require
functionSplitting into separate require()
statements saves gas
require(gamma1 != 0 && gamma2 != 0 && volumeGamma != 0, 'Gammas must be > 0');
Recommendation:
require(gamma1 != 0, 'gamma1 must be > 0'); require(gamma2 != 0, 'gamma2 must be > 0'); require(volumeGamma != 0, 'volumeGamma must be > 0');
Similarly for the following five require
statements:
!= 0
instead of > 0
in a require
statement if variable is an unsigned integer (uint
)!= 0
should be used where possible since > 0
costs more gas
require(currentLiquidity > 0, 'NP'); // Do not recalculate the empty ranges
Recommendation: Change currentLiquidity > 0
to currentLiquidity != 0
Similarly for the following five require
statements:
for
loopSince calculating the array length costs gas, it's best to read the length of the array from memory before executing the loop, as demonstrated below:
for (uint256 i = 0; i < secondsAgos.length; i++) { current = getSingleTimepoint(self, time, secondsAgos[i], tick, index, oldestIndex, liquidity); (tickCumulatives[i], secondsPerLiquidityCumulatives[i], volatilityCumulatives[i], volumePerAvgLiquiditys[i]) = ( current.tickCumulative, current.secondsPerLiquidityCumulative, current.volatilityCumulative, current.volumePerLiquidityCumulative ); }
Suggestion:
uint256 totalSecondsAgo = secondsAgos.length; for (uint256 i = 0; i < totalSecondsAgo; i++) { current = getSingleTimepoint(self, time, secondsAgos[i], tick, index, oldestIndex, liquidity); (tickCumulatives[i], secondsPerLiquidityCumulatives[i], volatilityCumulatives[i], volumePerAvgLiquiditys[i]) = ( current.tickCumulative, current.secondsPerLiquidityCumulative, current.volatilityCumulative, current.volumePerLiquidityCumulative ); }
++i
instead of i++
to increase count in a for
loopSince use of i++
(or equivalent counter) costs more gas, it is better to use ++i
, as demonstrated below:
for (uint256 i = 0; i < secondsAgos.length; i++) { current = getSingleTimepoint(self, time, secondsAgos[i], tick, index, oldestIndex, liquidity); (tickCumulatives[i], secondsPerLiquidityCumulatives[i], volatilityCumulatives[i], volumePerAvgLiquiditys[i]) = ( current.tickCumulative, current.secondsPerLiquidityCumulative, current.volatilityCumulative, current.volumePerLiquidityCumulative ); }
Suggestion:
uint256 totalSecondsAgo = secondsAgos.length; for (uint256 i = 0; i < totalSecondsAgo; ++i) { current = getSingleTimepoint(self, time, secondsAgos[i], tick, index, oldestIndex, liquidity); (tickCumulatives[i], secondsPerLiquidityCumulatives[i], volatilityCumulatives[i], volumePerAvgLiquiditys[i]) = ( current.tickCumulative, current.secondsPerLiquidityCumulative, current.volatilityCumulative, current.volumePerLiquidityCumulative ); }
for
loopUnderflow/overflow checks are made every time ++i
(or equivalent counter) is called. Such checks are unnecessary since i
is already limited. Therefore, use unchecked {++i}
instead, as shown below:
for (uint256 i = 0; i < secondsAgos.length; i++) { current = getSingleTimepoint(self, time, secondsAgos[i], tick, index, oldestIndex, liquidity); (tickCumulatives[i], secondsPerLiquidityCumulatives[i], volatilityCumulatives[i], volumePerAvgLiquiditys[i]) = ( current.tickCumulative, current.secondsPerLiquidityCumulative, current.volatilityCumulative, current.volumePerLiquidityCumulative ); }
Suggestion:
uint256 totalSecondsAgo = secondsAgos.length; for (uint256 i = 0; i < totalSecondsAgo;) { current = getSingleTimepoint(self, time, secondsAgos[i], tick, index, oldestIndex, liquidity); (tickCumulatives[i], secondsPerLiquidityCumulatives[i], volatilityCumulatives[i], volumePerAvgLiquiditys[i]) = ( current.tickCumulative, current.secondsPerLiquidityCumulative, current.volatilityCumulative, current.volumePerLiquidityCumulative ); unchecked { ++i; } }