QuickSwap and StellaSwap contest - IllIllI's results

A concentrated liquidity DEX with dynamic fees.

General Information

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

QuickSwap and StellaSwap

Findings Distribution

Researcher Performance

Rank: 16/113

Findings: 2

Award: $442.91

🌟 Selected for report: 1

šŸš€ Solo Findings: 0

Summary

Low Risk Issues

IssueInstances
[L‑01]There will be no liquidity cooldowns for the first few blocks in ~84 years1
[L‑02]TWAP answers will be wrong in ~84 years1
[L‑03]require() should be used instead of assert()1
[L‑04]Missing checks for address(0x0) when assigning values to address state variables7

Total: 10 instances over 4 issues

Non-critical Issues

IssueInstances
[N‑01]require()/revert() statements should have descriptive reason strings27
[N‑02]2**<n> - 1 should be re-written as type(uint<n>).max1
[N‑03]constants should be defined rather than using magic numbers41
[N‑04]Large multiples of ten should use scientific notation (e.g. 1e6) rather than decimal literals (e.g. 1000000), for readability1
[N‑05]Use bit shifts in an imutable variable rather than long bit masks of a single bit, for readability2
[N‑06]Use a more recent version of solidity5
[N‑07]Use a more recent version of solidity1
[N‑08]Lines are too long1
[N‑09]Variable names that consist of all capital letters should be reserved for constant/immutable variables2
[N‑10]File is missing NatSpec4
[N‑11]NatSpec is incomplete7
[N‑12]Not using the named return variables anywhere in the function is confusing33

Total: 125 instances over 12 issues

Low Risk Issues

[L‑01] There will be no liquidity cooldowns for the first few blocks in ~84 years

Since _blockTimestamp() truncates block.timestamp to uint32, once the current timestamp passes type(uint32).max, the subtraction below will underflow, causing the require() statement to pass, regardless of the time since the last liquidity addition

There is 1 instance of this issue:

File: /src/core/contracts/AlgebraPool.sol

229:           require((_blockTimestamp() - lastLiquidityAddTimestamp) >= _liquidityCooldown);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L229

[L‑02] TWAP answers will be wrong in ~84 years

Like the issue above, the timestamp in the calculation below will wrap, leading to answers about the TWAP value to be wrong

There is 1 instance of this issue:

File: /src/core/contracts/libraries/DataStorage.sol

133:       avgTick = (lastTimestamp == oldestTimestamp) ? tick : (lastTickCumulative - oldestTickCumulative) / (lastTimestamp - oldestTimestamp);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L133

[L‑03] require() should be used instead of assert()

Prior to solidity version 0.8.0, hitting an assert consumes the remainder of the transaction's available gas rather than returning it, as require()/revert() do. assert() should be avoided even past solidity version 0.8.0 as its documentation states that "The assert function creates an error of type Panic(uint256). ... Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix".

There is 1 instance of this issue:

File: src/core/contracts/libraries/DataStorage.sol

190:      assert(false);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L190

[L‑04] Missing checks for address(0x0) when assigning values to address state variables

There are 7 instances of this issue:

File: src/core/contracts/AlgebraFactory.sol

54:       poolDeployer = _poolDeployer;

55:       vaultAddress = _vaultAddress;

80:       owner = _owner;

87:       farmingAddress = _farmingAddress;

94:       vaultAddress = _vaultAddress;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraFactory.sol#L54

File: src/core/contracts/AlgebraPool.sol

961:      activeIncentive = virtualPoolAddress;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L961

File: src/core/contracts/DataStorageOperator.sol

33:       pool = _pool;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L33

Non-critical Issues

[N‑01] require()/revert() statements should have descriptive reason strings

There are 27 instances of this issue:

File: src/core/contracts/AlgebraFactory.sol

43:       require(msg.sender == owner);

60:       require(tokenA != tokenB);

62:       require(token0 != address(0));

63:       require(poolByPair[token0][token1] == address(0));

78:       require(owner != _owner);

85:       require(farmingAddress != _farmingAddress);

92:       require(vaultAddress != _vaultAddress);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraFactory.sol#L43

File: src/core/contracts/AlgebraPoolDeployer.sol

22:       require(msg.sender == factory);

27:       require(msg.sender == owner);

37:       require(_factory != address(0));

38:       require(factory == address(0));

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPoolDeployer.sol#L22

File: src/core/contracts/AlgebraPool.sol

55:       require(msg.sender == IAlgebraFactory(factory).owner());

122:        require(_lower.initialized);

134:        require(_upper.initialized);

229:            require((_blockTimestamp() - lastLiquidityAddTimestamp) >= _liquidityCooldown);

953:      require((communityFee0 <= Constants.MAX_COMMUNITY_FEE) && (communityFee1 <= Constants.MAX_COMMUNITY_FEE));

960:      require(msg.sender == IAlgebraFactory(factory).farmingAddress());

968:      require(newLiquidityCooldown <= Constants.MAX_LIQUIDITY_COOLDOWN && liquidityCooldown != newLiquidityCooldown);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L55

File: src/core/contracts/DataStorageOperator.sol

43:       require(msg.sender == factory || msg.sender == IAlgebraFactory(factory).owner());

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L43

File: src/core/contracts/libraries/DataStorage.sol

369:      require(!self[0].initialized);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L369

File: src/core/contracts/libraries/PriceMovementMath.sol

52:       require(price > 0);

53:       require(liquidity > 0);

70:           require((product = amount * price) / amount == price); // if the product overflows, we know the denominator underflows

71:           require(liquidityShifted > product); // in addition, we must check that the denominator does not underflow

87:           require(price > quotient);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/PriceMovementMath.sol#L52

File: src/core/contracts/libraries/TokenDeltaMath.sol

30:       require(priceDelta < priceUpper); // forbids underflow and 0 priceLower

51:       require(priceUpper >= priceLower);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TokenDeltaMath.sol#L30

[N‑02] 2**<n> - 1 should be re-written as type(uint<n>).max

Earlier versions of solidity can use uint<n>(-1) instead. Expressions not including the - 1 can often be re-written to accomodate the change (e.g. by using a > rather than a >=, which will also save some gas)

There is 1 instance of this issue:

File: src/core/contracts/DataStorageOperator.sol

138:      if (volume >= 2**192) volumeShifted = (type(uint256).max) / (liquidity > 0 ? liquidity : 1);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L138

[N‑03] constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

There are 41 instances of this issue:

File: src/core/contracts/AlgebraPool.sol

/// @audit 0xFFFFFF
/// @audit 0xFFFFFF
410:        key := or(shl(24, or(shl(24, owner), and(bottomTick, 0xFFFFFF))), and(topTick, 0xFFFFFF))

/// @audit 1e6
905:        fee0 = FullMath.mulDivRoundingUp(amount0, _fee, 1e6);

/// @audit 1e6
912:        fee1 = FullMath.mulDivRoundingUp(amount1, _fee, 1e6);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L410

File: src/core/contracts/DataStorageOperator.sol

/// @audit 192
138:      if (volume >= 2**192) volumeShifted = (type(uint256).max) / (liquidity > 0 ? liquidity : 1);

/// @audit 64
139:      else volumeShifted = (volume << 64) / (liquidity > 0 ? liquidity : 1);

/// @audit 15
158:      return AdaptiveFee.getFee(volatilityAverage / 15, volumePerLiqAverage, feeConfig);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L138

File: src/core/contracts/libraries/AdaptiveFee.sol

/// @audit 6
52:         if (x >= 6 * uint256(g)) return alpha; // so x < 19 bits

/// @audit 8
53:         uint256 g8 = uint256(g)**8; // < 128 bits (8*16)

/// @audit 6
59:         if (x >= 6 * uint256(g)) return 0; // so x < 19 bits

/// @audit 8
60:         uint256 g8 = uint256(g)**8; // < 128 bits (8*16)

/// @audit 6
92:       res += (xLowestDegree * gHighestDegree) / 6;

/// @audit 24
96:       res += (xLowestDegree * gHighestDegree) / 24;

/// @audit 120
100:      res += (xLowestDegree * gHighestDegree) / 120;

/// @audit 720
104:      res += (xLowestDegree * gHighestDegree) / 720;

/// @audit 5040
107:      res += (xLowestDegree * g) / 5040 + (xLowestDegree * x) / (40320);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/AdaptiveFee.sol#L52

File: src/core/contracts/libraries/DataStorage.sol

/// @audit 6
/// @audit 6
/// @audit 6
53:       volatility = uint256((K**2 * sumOfSquares + 6 * B * K * sumOfSequence + 6 * dt * B**2) / (6 * dt**2));

/// @audit 128
80:       last.secondsPerLiquidityCumulative += ((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1)); // just timedelta if liquidity == 0

/// @audit 57
348:          uint256(endOfWindow.volumePerLiquidityCumulative - startOfWindow.volumePerLiquidityCumulative) >> 57

/// @audit 57
355:          uint256(endOfWindow.volumePerLiquidityCumulative - _oldestVolumePerLiquidityCumulative) >> 57

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L53

File: src/core/contracts/libraries/PriceMovementMath.sol

/// @audit 1e6
/// @audit 1e6
157:        uint256 amountAvailableAfterFee = FullMath.mulDiv(uint256(amountAvailable), 1e6 - fee, 1e6);

/// @audit 1e6
161:          feeAmount = FullMath.mulDivRoundingUp(input, fee, 1e6 - fee);

/// @audit 1e6
170:            feeAmount = FullMath.mulDivRoundingUp(input, fee, 1e6 - fee);

/// @audit 1e6
195:        feeAmount = FullMath.mulDivRoundingUp(input, fee, 1e6 - fee);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/PriceMovementMath.sol#L157

File: src/core/contracts/libraries/TickTable.sol

/// @audit 0xFF
21:         bitNumber := and(tick, 0xFF)

/// @audit 0x5555555555555555555555555555555555555555555555555555555555555555
32:         singleBitPos := iszero(and(word, 0x5555555555555555555555555555555555555555555555555555555555555555))

/// @audit 0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
33:         singleBitPos := or(singleBitPos, shl(7, iszero(and(word, 0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))))

/// @audit 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF
34:         singleBitPos := or(singleBitPos, shl(6, iszero(and(word, 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF))))

/// @audit 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF
35:         singleBitPos := or(singleBitPos, shl(5, iszero(and(word, 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF))))

/// @audit 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF
36:         singleBitPos := or(singleBitPos, shl(4, iszero(and(word, 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF))))

/// @audit 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF
37:         singleBitPos := or(singleBitPos, shl(3, iszero(and(word, 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF))))

/// @audit 0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F
38:         singleBitPos := or(singleBitPos, shl(2, iszero(and(word, 0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F))))

/// @audit 0x3333333333333333333333333333333333333333333333333333333333333333
39:         singleBitPos := or(singleBitPos, shl(1, iszero(and(word, 0x3333333333333333333333333333333333333333333333333333333333333333))))

/// @audit 0xFF
86:           bitNumber := and(tick, 0xFF)

/// @audit 255
89:         uint256 _row = self[rowNumber] << (255 - bitNumber); // all the 1s at or to the right of the current bitNumber

/// @audit 255
92:           tick -= int24(255 - getMostSignificantBit(_row));

/// @audit 0xFF
104:          bitNumber := and(tick, 0xFF)

/// @audit 255
115:          tick += int24(255 - bitNumber);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TickTable.sol#L21

[N‑04] Large multiples of ten should use scientific notation (e.g. 1e6) rather than decimal literals (e.g. 1000000), for readability

There is 1 instance of this issue:

File: src/core/contracts/DataStorageOperator.sol

16:     uint128 constant MAX_VOLUME_PER_LIQUIDITY = 100000 << 64; // maximum meaningful ratio of volume to liquidity

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L16

[N‑05] Use bit shifts in an imutable variable rather than long bit masks of a single bit, for readability

There are 2 instances of this issue:

File: src/core/contracts/libraries/Constants.sol

6:      uint256 internal constant Q96 = 0x1000000000000000000000000;

7:      uint256 internal constant Q128 = 0x100000000000000000000000000000000;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/Constants.sol#L6

[N‑06] Use a more recent version of solidity

Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions

There are 5 instances of this issue:

File: src/core/contracts/AlgebraPool.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L2

File: src/core/contracts/DataStorageOperator.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L2

File: src/core/contracts/libraries/PriceMovementMath.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/PriceMovementMath.sol#L2

File: src/core/contracts/libraries/TickManager.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TickManager.sol#L2

File: src/core/contracts/libraries/TokenDeltaMath.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TokenDeltaMath.sol#L2

[N‑07] Use a more recent version of solidity

Use a solidity version of at least 0.8.4 to get bytes.concat() instead of abi.encodePacked(<bytes>,<bytes>) Use a solidity version of at least 0.8.12 to get string.concat() instead of abi.encodePacked(<str>,<str>)

There is 1 instance of this issue:

File: src/core/contracts/AlgebraFactory.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraFactory.sol#L2

[N‑08] Lines are too long

Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length

There is 1 instance of this issue:

File: src/core/contracts/libraries/AdaptiveFee.sol

38:       return uint16(config.baseFee + sigmoid(volumePerLiquidity, config.volumeGamma, uint16(sumOfSigmoids), config.volumeBeta)); // safe since alpha1 + alpha2 + baseFee _must_ be <= type(uint16).max

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/AdaptiveFee.sol#L38

[N‑09] Variable names that consist of all capital letters should be reserved for constant/immutable variables

If the variable needs to be different based on which class it comes from, a view/pure function should be used instead (e.g. like this).

There are 2 instances of this issue:

File: src/core/contracts/libraries/DataStorage.sol

49:       int256 K = (tick1 - tick0) - (avgTick1 - avgTick0); // (k - p)*dt

50:       int256 B = (tick0 - avgTick0) * dt; // (b - q)*dt

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L49

[N‑10] File is missing NatSpec

There are 4 instances of this issue:

File: src/core/contracts/AlgebraPoolDeployer.sol

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPoolDeployer.sol

File: src/core/contracts/base/PoolImmutables.sol

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/base/PoolImmutables.sol

File: src/core/contracts/DataStorageOperator.sol

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol

File: src/core/contracts/libraries/Constants.sol

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/Constants.sol

[N‑11] NatSpec is incomplete

There are 7 instances of this issue:

File: src/core/contracts/AlgebraPool.sol

/// @audit Missing: '@param owner'
/// @audit Missing: '@param bottomTick'
/// @audit Missing: '@param topTick'
/// @audit Missing: '@param liquidityDelta'
268     /**
269      * @dev Updates position's ticks and its fees
270      * @return position The Position object to operate with
271      * @return amount0 The amount of token0 the caller needs to send, negative if the pool needs to send it
272      * @return amount1 The amount of token1 the caller needs to send, negative if the pool needs to send it
273      */
274     function _updatePositionTicksAndFees(
275       address owner,
276       int24 bottomTick,
277       int24 topTick,
278       int128 liquidityDelta
279     )
280       private
281       returns (
282         Position storage position,
283         int256 amount0,
284:        int256 amount1

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L268-L284

File: src/core/contracts/libraries/PriceMovementMath.sol

/// @audit Missing: '@param zeroToOne'
125     /// @notice Computes the result of swapping some amount in, or amount out, given the parameters of the swap
126     /// @dev The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive
127     /// @param currentPrice The current Q64.96 sqrt price of the pool
128     /// @param targetPrice The Q64.96 sqrt price that cannot be exceeded, from which the direction of the swap is inferred
129     /// @param liquidity The usable liquidity
130     /// @param amountAvailable How much input or output amount is remaining to be swapped in/out
131     /// @param fee The fee taken from the input amount, expressed in hundredths of a bip
132     /// @return resultPrice The Q64.96 sqrt price after swapping the amount in/out, not to exceed the price target
133     /// @return input The amount to be swapped in, of either token0 or token1, based on the direction of the swap
134     /// @return output The amount to be received, of either token0 or token1, based on the direction of the swap
135     /// @return feeAmount The amount of input that will be taken as a fee
136     function movePriceTowardsTarget(
137       bool zeroToOne,
138       uint160 currentPrice,
139       uint160 targetPrice,
140       uint128 liquidity,
141       int256 amountAvailable,
142       uint16 fee
143     )
144       internal
145       pure
146       returns (
147         uint160 resultPrice,
148         uint256 input,
149         uint256 output,
150:        uint256 feeAmount

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/PriceMovementMath.sol#L125-L150

File: src/core/contracts/libraries/TickTable.sol

/// @audit Missing: '@return'
28      /// @dev it is assumed that word contains exactly one 1-bit, otherwise the result will be incorrect
29      /// @param word The word containing only one 1-bit
30:     function getSingleSignificantBit(uint256 word) internal pure returns (uint8 singleBitPos) {

/// @audit Missing: '@return'
44      /// @dev it is assumed that before the call, a check will be made that the argument (word) is not equal to zero
45      /// @param word The word containing at least one 1-bit
46:     function getMostSignificantBit(uint256 word) internal pure returns (uint8 mostBitPos) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TickTable.sol#L28-L30

[N‑12] Not using the named return variables anywhere in the function is confusing

Consider changing the variable to be an unnamed one

There are 33 instances of this issue:

File: src/core/contracts/AlgebraPool.sol

/// @audit initialized
/// @audit blockTimestamp
/// @audit tickCumulative
/// @audit secondsPerLiquidityCumulative
/// @audit volatilityCumulative
/// @audit averageTick
/// @audit volumePerLiquidityCumulative
79      function timepoints(uint256 index)
80        external
81        view
82        override
83        returns (
84          bool initialized,
85          uint32 blockTimestamp,
86          int56 tickCumulative,
87          uint160 secondsPerLiquidityCumulative,
88          uint88 volatilityCumulative,
89          int24 averageTick,
90:         uint144 volumePerLiquidityCumulative

/// @audit innerTickCumulative
/// @audit innerSecondsSpentPerLiquidity
/// @audit innerSecondsSpent
103     function getInnerCumulatives(int24 bottomTick, int24 topTick)
104       external
105       view
106       override
107       onlyValidTicks(bottomTick, topTick)
108       returns (
109         int56 innerTickCumulative,
110         uint160 innerSecondsSpentPerLiquidity,
111:        uint32 innerSecondsSpent

/// @audit tickCumulatives
/// @audit secondsPerLiquidityCumulatives
/// @audit volatilityCumulatives
/// @audit volumePerAvgLiquiditys
171     function getTimepoints(uint32[] calldata secondsAgos)
172       external
173       view
174       override
175       returns (
176         int56[] memory tickCumulatives,
177         uint160[] memory secondsPerLiquidityCumulatives,
178         uint112[] memory volatilityCumulatives,
179:        uint256[] memory volumePerAvgLiquiditys

/// @audit newTimepointIndex
551     function _writeTimepoint(
552       uint16 timepointIndex,
553       uint32 blockTimestamp,
554       int24 tick,
555       uint128 liquidity,
556       uint128 volumePerLiquidityInBlock
557:    ) private returns (uint16 newTimepointIndex) {

/// @audit tickCumulative
/// @audit secondsPerLiquidityCumulative
/// @audit volatilityCumulative
/// @audit volumePerAvgLiquidity
561     function _getSingleTimepoint(
562       uint32 blockTimestamp,
563       uint32 secondsAgo,
564       int24 startTick,
565       uint16 timepointIndex,
566       uint128 liquidityStart
567     )
568       private
569       view
570       returns (
571         int56 tickCumulative,
572         uint160 secondsPerLiquidityCumulative,
573         uint112 volatilityCumulative,
574:        uint256 volumePerAvgLiquidity

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L79-L90

File: src/core/contracts/DataStorageOperator.sol

/// @audit tickCumulatives
/// @audit secondsPerLiquidityCumulatives
/// @audit volatilityCumulatives
/// @audit volumePerAvgLiquiditys
88      function getTimepoints(
89        uint32 time,
90        uint32[] memory secondsAgos,
91        int24 tick,
92        uint16 index,
93        uint128 liquidity
94      )
95        external
96        view
97        override
98        onlyPool
99        returns (
100         int56[] memory tickCumulatives,
101         uint160[] memory secondsPerLiquidityCumulatives,
102         uint112[] memory volatilityCumulatives,
103:        uint256[] memory volumePerAvgLiquiditys

/// @audit TWVolatilityAverage
/// @audit TWVolumePerLiqAverage
110     function getAverages(
111       uint32 time,
112       int24 tick,
113       uint16 index,
114       uint128 liquidity
115:    ) external view override onlyPool returns (uint112 TWVolatilityAverage, uint256 TWVolumePerLiqAverage) {

/// @audit indexUpdated
120     function write(
121       uint16 index,
122       uint32 blockTimestamp,
123       int24 tick,
124       uint128 liquidity,
125       uint128 volumePerLiquidity
126:    ) external override onlyPool returns (uint16 indexUpdated) {

/// @audit fee
150     function getFee(
151       uint32 _time,
152       int24 _tick,
153       uint16 _index,
154       uint128 _liquidity
155:    ) external view override onlyPool returns (uint16 fee) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L88-L103

File: src/core/contracts/libraries/AdaptiveFee.sol

/// @audit fee
25      function getFee(
26        uint88 volatility,
27        uint256 volumePerLiquidity,
28        Configuration memory config
29:     ) internal pure returns (uint16 fee) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/AdaptiveFee.sol#L25-L29

File: src/core/contracts/libraries/DataStorage.sol

/// @audit targetTimepoint
205     function getSingleTimepoint(
206       Timepoint[UINT16_MODULO] storage self,
207       uint32 time,
208       uint32 secondsAgo,
209       int24 tick,
210       uint16 index,
211       uint16 oldestIndex,
212       uint128 liquidity
213:    ) internal view returns (Timepoint memory targetTimepoint) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L205-L213

File: src/core/contracts/libraries/PriceMovementMath.sol

/// @audit resultPrice
20      function getNewPriceAfterInput(
21        uint160 price,
22        uint128 liquidity,
23        uint256 input,
24        bool zeroToOne
25:     ) internal pure returns (uint160 resultPrice) {

/// @audit resultPrice
36      function getNewPriceAfterOutput(
37        uint160 price,
38        uint128 liquidity,
39        uint256 output,
40        bool zeroToOne
41:     ) internal pure returns (uint160 resultPrice) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/PriceMovementMath.sol#L20-L25

File: src/core/contracts/libraries/TickManager.sol

/// @audit liquidityDelta
129     function cross(
130       mapping(int24 => Tick) storage self,
131       int24 tick,
132       uint256 totalFeeGrowth0Token,
133       uint256 totalFeeGrowth1Token,
134       uint160 secondsPerLiquidityCumulative,
135       int56 tickCumulative,
136       uint32 time
137:    ) internal returns (int128 liquidityDelta) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TickManager.sol#L129-L137

File: src/core/contracts/libraries/TickTable.sol

/// @audit mostBitPos
46:     function getMostSignificantBit(uint256 word) internal pure returns (uint8 mostBitPos) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TickTable.sol#L46

Summary

Gas Optimizations

IssueInstancesTotal Gas Saved
[G‑01]State variables only set in the constructor should be declared immutable24194
[G‑02]Using calldata instead of memory for read-only arguments in external functions saves gas1120
[G‑03]Using storage instead of memory for structs/arrays saves gas14200
[G‑04]Avoid contract existence checks by using low level calls242400
[G‑05]State variables should be cached in stack variables rather than re-reading them from storage7679
[G‑06]internal functions only called once can be inlined to save gas240
[G‑07]<array>.length should not be looked up in every loop of a for-loop13
[G‑08]Use a more recent version of solidity13-
[G‑09]Using > 0 costs more gas than != 0 when used on a uint in a require() statement636
[G‑10]>= costs less gas than >26
[G‑11]++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)15
[G‑12]Splitting require() statements that use && saves gas618
[G‑13]Using private rather than public for constants, saves gas1-
[G‑14]Division by two should use bit shifting120
[G‑15]Use custom errors rather than revert()/require() strings to save gas32-
[G‑16]Functions guaranteed to revert when called by normal users can be marked payable17357

Total: 117 instances over 16 issues with 12078 gas saved

Gas totals use lower bounds of ranges and count two iterations of each for-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions

Gas Optimizations

[G‑01] State variables only set in the constructor should be declared immutable

Avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32 (3 gas).

While strings are not value types, and therefore cannot be immutable/constant if not hard-coded outside of the constructor, the same behavior can be achieved by making the current contract abstract with virtual functions for the string accessors, and having a child contract override the functions with the hard-coded implementation-specific values.

There are 2 instances of this issue:

File: src/core/contracts/AlgebraPoolDeployer.sol

/// @audit owner (access)
27:       require(msg.sender == owner);

/// @audit owner (constructor)
32:       owner = msg.sender;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPoolDeployer.sol#L27

[G‑02] Using calldata instead of memory for read-only arguments in external functions saves gas

When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as having memory arguments, it's still valid for implementation contracs to use calldata arguments instead.

If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one

Note that I've also flagged instances where the function is public but can be marked as external since it's not called by the contract, and cases where a constructor is involved

There is 1 instance of this issue:

File: src/core/contracts/DataStorageOperator.sol

/// @audit secondsAgos
88      function getTimepoints(
89        uint32 time,
90        uint32[] memory secondsAgos,
91        int24 tick,
92        uint16 index,
93        uint128 liquidity
94      )
95        external
96        view
97        override
98        onlyPool
99        returns (
100         int56[] memory tickCumulatives,
101         uint160[] memory secondsPerLiquidityCumulatives,
102         uint112[] memory volatilityCumulatives,
103:        uint256[] memory volumePerAvgLiquiditys

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L88-L103

[G‑03] Using storage instead of memory for structs/arrays saves gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct

There is 1 instance of this issue:

File: src/core/contracts/libraries/DataStorage.sol

397:      Timepoint memory last = _last;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L397

[G‑04] Avoid contract existence checks by using low level calls

Prior to 0.8.10 the compiler inserted extra code, including EXTCODESIZE (100 gas), to check for contract existence for external function calls. In more recent solidity versions, the compiler will not insert these checks if the external call has a return value. Similar behavior can be achieved in earlier versions by using low-level calls, since low level calls never check for contract existence

There are 24 instances of this issue:

File: src/core/contracts/AlgebraFactory.sol

/// @audit deploy()
69:       pool = IAlgebraPoolDeployer(poolDeployer).deploy(address(dataStorage), address(this), token0, token1);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraFactory.sol#L69

File: src/core/contracts/AlgebraPool.sol

/// @audit owner()
55:       require(msg.sender == IAlgebraFactory(factory).owner());

/// @audit balanceOf()
71:       return IERC20Minimal(token0).balanceOf(address(this));

/// @audit balanceOf()
75:       return IERC20Minimal(token1).balanceOf(address(this));

/// @audit timepoints()
93:       return IDataStorageOperator(dataStorageOperator).timepoints(index);

/// @audit getTimepoints()
183:        IDataStorageOperator(dataStorageOperator).getTimepoints(

/// @audit algebraMintCallback()
453:        IAlgebraMintCallback(msg.sender).algebraMintCallback(amount0, amount1, data);

/// @audit getFee()
542:      newFee = IDataStorageOperator(dataStorageOperator).getFee(_time, _tick, _index, _liquidity);

/// @audit vaultAddress()
547:      address vault = IAlgebraFactory(factory).vaultAddress();

/// @audit write()
558:      return IDataStorageOperator(dataStorageOperator).write(timepointIndex, blockTimestamp, tick, liquidity, volumePerLiquidityInBlock);

/// @audit getSingleTimepoint()
577:      return IDataStorageOperator(dataStorageOperator).getSingleTimepoint(blockTimestamp, secondsAgo, startTick, timepointIndex, liquidityStart);

/// @audit algebraSwapCallback()
585:      IAlgebraSwapCallback(msg.sender).algebraSwapCallback(amount0, amount1, data);

/// @audit increaseCumulative()
753:          IAlgebraVirtualPool.Status _status = IAlgebraVirtualPool(activeIncentive).increaseCumulative(blockTimestamp);

/// @audit cross()
833:              IAlgebraVirtualPool(activeIncentive).cross(step.nextTick, zeroToOne);

/// @audit calculateVolumePerLiquidity()
880:        cache.volumePerLiquidityInBlock + IDataStorageOperator(dataStorageOperator).calculateVolumePerLiquidity(currentLiquidity, amount0, amount1)

/// @audit algebraFlashCallback()
916:      IAlgebraFlashCallback(msg.sender).algebraFlashCallback(fee0, fee1, data);

/// @audit vaultAddress()
918:      address vault = IAlgebraFactory(factory).vaultAddress();

/// @audit farmingAddress()
960:      require(msg.sender == IAlgebraFactory(factory).farmingAddress());

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L55

File: src/core/contracts/base/PoolImmutables.sol

/// @audit parameters()
30:       (dataStorageOperator, factory, token0, token1) = IAlgebraPoolDeployer(deployer).parameters();

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/base/PoolImmutables.sol#L30

File: src/core/contracts/DataStorageOperator.sol

/// @audit owner()
43:       require(msg.sender == factory || msg.sender == IAlgebraFactory(factory).owner());

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L43

File: src/core/contracts/libraries/TokenDeltaMath.sol

/// @audit toInt256()
67:         ? getToken0Delta(priceLower, priceUpper, uint128(liquidity), true).toInt256()

/// @audit toInt256()
68:         : -getToken0Delta(priceLower, priceUpper, uint128(-liquidity), false).toInt256();

/// @audit toInt256()
82:         ? getToken1Delta(priceLower, priceUpper, uint128(liquidity), true).toInt256()

/// @audit toInt256()
83:         : -getToken1Delta(priceLower, priceUpper, uint128(-liquidity), false).toInt256();

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TokenDeltaMath.sol#L67

[G‑05] State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces 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.

There are 7 instances of this issue:

File: src/core/contracts/AlgebraPool.sol

/// @audit totalFeeGrowth0Token on line 740
/// @audit totalFeeGrowth1Token on line 744
829:              cache.totalFeeGrowthB = zeroToOne ? totalFeeGrowth1Token : totalFeeGrowth0Token;

/// @audit liquidity on line 297
354:          uint128 liquidityBefore = liquidity;

/// @audit liquidity on line 736
/// @audit volumePerLiquidityInBlock on line 736
878:      (liquidity, volumePerLiquidityInBlock) = (

/// @audit activeIncentive on line 752
753:          IAlgebraVirtualPool.Status _status = IAlgebraVirtualPool(activeIncentive).increaseCumulative(blockTimestamp);

/// @audit activeIncentive on line 755
833:              IAlgebraVirtualPool(activeIncentive).cross(step.nextTick, zeroToOne);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L829

[G‑06] internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

There are 2 instances of this issue:

File: src/core/contracts/AlgebraFactory.sol

122:    function computeAddress(address token0, address token1) internal view returns (address pool) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraFactory.sol#L122

File: src/core/contracts/AlgebraPool.sol

215     function _recalculatePosition(
216       Position storage _position,
217       int128 liquidityDelta,
218       uint256 innerFeeGrowth0Token,
219:      uint256 innerFeeGrowth1Token

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L215-L219

[G‑07] <array>.length should not be looked up in every loop of a for-loop

The overheads outlined below are PER LOOP, excluding the first loop

  • storage arrays incur a Gwarmaccess (100 gas)
  • memory arrays use MLOAD (3 gas)
  • calldata arrays use CALLDATALOAD (3 gas)

Caching the length changes each of these to a DUP<N> (3 gas), and gets rid of the extra DUP<N> needed to store the stack offset

There is 1 instance of this issue:

File: src/core/contracts/libraries/DataStorage.sol

307:      for (uint256 i = 0; i < secondsAgos.length; i++) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L307

[G‑08] Use a more recent version of solidity

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

There are 13 instances of this issue:

File: src/core/contracts/AlgebraFactory.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraFactory.sol#L2

File: src/core/contracts/AlgebraPoolDeployer.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPoolDeployer.sol#L2

File: src/core/contracts/AlgebraPool.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L2

File: src/core/contracts/base/PoolImmutables.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/base/PoolImmutables.sol#L2

File: src/core/contracts/base/PoolState.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/base/PoolState.sol#L2

File: src/core/contracts/DataStorageOperator.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L2

File: src/core/contracts/libraries/AdaptiveFee.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/AdaptiveFee.sol#L2

File: src/core/contracts/libraries/Constants.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/Constants.sol#L2

File: src/core/contracts/libraries/DataStorage.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L2

File: src/core/contracts/libraries/PriceMovementMath.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/PriceMovementMath.sol#L2

File: src/core/contracts/libraries/TickManager.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TickManager.sol#L2

File: src/core/contracts/libraries/TickTable.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TickTable.sol#L2

File: src/core/contracts/libraries/TokenDeltaMath.sol

2:    pragma solidity =0.7.6;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TokenDeltaMath.sol#L2

[G‑09] Using > 0 costs more gas than != 0 when used on a uint in a require() statement

This change saves 6 gas per instance. The optimization works until solidity version 0.8.13 where there is a regression in gas costs.

There are 6 instances of this issue:

File: src/core/contracts/AlgebraPool.sol

224:        require(currentLiquidity > 0, 'NP'); // Do not recalculate the empty ranges

434:      require(liquidityDesired > 0, 'IL');

469:      require(liquidityActual > 0, 'IIL2');

898:      require(_liquidity > 0, 'L');

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L224

File: src/core/contracts/libraries/PriceMovementMath.sol

52:       require(price > 0);

53:       require(liquidity > 0);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/PriceMovementMath.sol#L52

[G‑10] >= costs less gas than >

The compiler uses opcodes GT and ISZERO for solidity code that uses >, but only requires LT for >=, which saves 3 gas

There are 2 instances of this issue:

File: src/core/contracts/AlgebraPool.sol

498:      amount0 = amount0Requested > positionFees0 ? positionFees0 : amount0Requested;

499:      amount1 = amount1Requested > positionFees1 ? positionFees1 : amount1Requested;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L498

[G‑11] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)

Saves 5 gas per loop

There is 1 instance of this issue:

File: src/core/contracts/libraries/DataStorage.sol

307:      for (uint256 i = 0; i < secondsAgos.length; i++) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L307

[G‑12] Splitting require() statements that use && saves gas

See this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper by 3 gas

There are 6 instances of this issue:

File: src/core/contracts/AlgebraFactory.sol

110:      require(gamma1 != 0 && gamma2 != 0 && volumeGamma != 0, 'Gammas must be > 0');

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraFactory.sol#L110

File: src/core/contracts/AlgebraPool.sol

739:          require(limitSqrtPrice < currentPrice && limitSqrtPrice > TickMath.MIN_SQRT_RATIO, 'SPL');

743:          require(limitSqrtPrice > currentPrice && limitSqrtPrice < TickMath.MAX_SQRT_RATIO, 'SPL');

953:      require((communityFee0 <= Constants.MAX_COMMUNITY_FEE) && (communityFee1 <= Constants.MAX_COMMUNITY_FEE));

968:      require(newLiquidityCooldown <= Constants.MAX_LIQUIDITY_COOLDOWN && liquidityCooldown != newLiquidityCooldown);

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L739

File: src/core/contracts/DataStorageOperator.sol

46:       require(_feeConfig.gamma1 != 0 && _feeConfig.gamma2 != 0 && _feeConfig.volumeGamma != 0, 'Gammas must be > 0');

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L46

[G‑13] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table

There is 1 instance of this issue:

File: src/core/contracts/libraries/DataStorage.sol

12:     uint32 public constant WINDOW = 1 days;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L12

[G‑14] Division by two should use bit shifting

<x> / 2 is the same as <x> >> 1. While the compiler uses the SHR opcode to accomplish both, the version that uses division incurs an overhead of 20 gas due to JUMPs to and from a compiler utility function that introduces checks which can be avoided by using unchecked {} around the division by two

There is 1 instance of this issue:

File: src/core/contracts/libraries/AdaptiveFee.sol

88:       res += (xLowestDegree * gHighestDegree) / 2;

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/AdaptiveFee.sol#L88

[G‑15] Use custom errors rather than revert()/require() strings to save gas

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas

There are 32 instances of this issue:

File: src/core/contracts/AlgebraFactory.sol

109:      require(uint256(alpha1) + uint256(alpha2) + uint256(baseFee) <= type(uint16).max, 'Max fee exceeded');

110:      require(gamma1 != 0 && gamma2 != 0 && volumeGamma != 0, 'Gammas must be > 0');

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraFactory.sol#L109

File: src/core/contracts/AlgebraPool.sol

60:       require(topTick < TickMath.MAX_TICK + 1, 'TUM');

61:       require(topTick > bottomTick, 'TLU');

62:       require(bottomTick > TickMath.MIN_TICK - 1, 'TLM');

194:      require(globalState.price == 0, 'AI');

224:        require(currentLiquidity > 0, 'NP'); // Do not recalculate the empty ranges

434:      require(liquidityDesired > 0, 'IL');

454:        if (amount0 > 0) require((receivedAmount0 = balanceToken0() - receivedAmount0) > 0, 'IIAM');

455:        if (amount1 > 0) require((receivedAmount1 = balanceToken1() - receivedAmount1) > 0, 'IIAM');

469:      require(liquidityActual > 0, 'IIL2');

474:        require((amount0 = uint256(amount0Int)) <= receivedAmount0, 'IIAM2');

475:        require((amount1 = uint256(amount1Int)) <= receivedAmount1, 'IIAM2');

608:        require(balance0Before.add(uint256(amount0)) <= balanceToken0(), 'IIA');

614:        require(balance1Before.add(uint256(amount1)) <= balanceToken1(), 'IIA');

636:      require(globalState.unlocked, 'LOK');

641:        require((amountRequired = int256(balanceToken0().sub(balance0Before))) > 0, 'IIA');

645:        require((amountRequired = int256(balanceToken1().sub(balance1Before))) > 0, 'IIA');

731:        require(unlocked, 'LOK');

733:        require(amountRequired != 0, 'AS');

739:          require(limitSqrtPrice < currentPrice && limitSqrtPrice > TickMath.MIN_SQRT_RATIO, 'SPL');

743:          require(limitSqrtPrice > currentPrice && limitSqrtPrice < TickMath.MAX_SQRT_RATIO, 'SPL');

898:      require(_liquidity > 0, 'L');

921:      require(balance0Before.add(fee0) <= paid0, 'F0');

935:      require(balance1Before.add(fee1) <= paid1, 'F1');

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L60

File: src/core/contracts/base/PoolState.sol

41:       require(globalState.unlocked, 'LOK');

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/base/PoolState.sol#L41

File: src/core/contracts/DataStorageOperator.sol

27:       require(msg.sender == pool, 'only pool can call this');

45:       require(uint256(_feeConfig.alpha1) + uint256(_feeConfig.alpha2) + uint256(_feeConfig.baseFee) <= type(uint16).max, 'Max fee exceeded');

46:       require(_feeConfig.gamma1 != 0 && _feeConfig.gamma2 != 0 && _feeConfig.volumeGamma != 0, 'Gammas must be > 0');

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L27

File: src/core/contracts/libraries/DataStorage.sol

238:      require(lteConsideringOverflow(self[oldestIndex].blockTimestamp, target, time), 'OLD');

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/DataStorage.sol#L238

File: src/core/contracts/libraries/TickManager.sol

96:       require(liquidityTotalAfter < Constants.MAX_LIQUIDITY_PER_TICK + 1, 'LO');

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TickManager.sol#L96

File: src/core/contracts/libraries/TickTable.sol

15:       require(tick % Constants.TICK_SPACING == 0, 'tick is not spaced'); // ensure that the tick is spaced

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/libraries/TickTable.sol#L15

[G‑16] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 17 instances of this issue:

File: src/core/contracts/AlgebraFactory.sol

77:     function setOwner(address _owner) external override onlyOwner {

84:     function setFarmingAddress(address _farmingAddress) external override onlyOwner {

91:     function setVaultAddress(address _vaultAddress) external override onlyOwner {

98      function setBaseFeeConfiguration(
99        uint16 alpha1,
100       uint16 alpha2,
101       uint32 beta1,
102       uint32 beta2,
103       uint16 gamma1,
104       uint16 gamma2,
105       uint32 volumeBeta,
106       uint16 volumeGamma,
107       uint16 baseFee
108:    ) external override onlyOwner {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraFactory.sol#L77

File: src/core/contracts/AlgebraPoolDeployer.sol

36:     function setFactory(address _factory) external override onlyOwner {

44      function deploy(
45        address dataStorage,
46        address _factory,
47        address token0,
48        address token1
49:     ) external override onlyFactory returns (address pool) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPoolDeployer.sol#L36

File: src/core/contracts/AlgebraPool.sol

103     function getInnerCumulatives(int24 bottomTick, int24 topTick)
104       external
105       view
106       override
107       onlyValidTicks(bottomTick, topTick)
108       returns (
109         int56 innerTickCumulative,
110         uint160 innerSecondsSpentPerLiquidity,
111:        uint32 innerSecondsSpent

416     function mint(
417       address sender,
418       address recipient,
419       int24 bottomTick,
420       int24 topTick,
421       uint128 liquidityDesired,
422       bytes calldata data
423     )
424       external
425       override
426       lock
427       onlyValidTicks(bottomTick, topTick)
428       returns (
429         uint256 amount0,
430         uint256 amount1,
431:        uint128 liquidityActual

513     function burn(
514       int24 bottomTick,
515       int24 topTick,
516       uint128 amount
517:    ) external override lock onlyValidTicks(bottomTick, topTick) returns (uint256 amount0, uint256 amount1) {

952:    function setCommunityFee(uint8 communityFee0, uint8 communityFee1) external override lock onlyFactoryOwner {

967:    function setLiquidityCooldown(uint32 newLiquidityCooldown) external override onlyFactoryOwner {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/AlgebraPool.sol#L103-L111

File: src/core/contracts/DataStorageOperator.sol

37:     function initialize(uint32 time, int24 tick) external override onlyPool {

53      function getSingleTimepoint(
54        uint32 time,
55        uint32 secondsAgo,
56        int24 tick,
57        uint16 index,
58        uint128 liquidity
59      )
60        external
61        view
62        override
63        onlyPool
64        returns (
65          int56 tickCumulative,
66          uint160 secondsPerLiquidityCumulative,
67          uint112 volatilityCumulative,
68:         uint256 volumePerAvgLiquidity

88      function getTimepoints(
89        uint32 time,
90        uint32[] memory secondsAgos,
91        int24 tick,
92        uint16 index,
93        uint128 liquidity
94      )
95        external
96        view
97        override
98        onlyPool
99        returns (
100         int56[] memory tickCumulatives,
101         uint160[] memory secondsPerLiquidityCumulatives,
102         uint112[] memory volatilityCumulatives,
103:        uint256[] memory volumePerAvgLiquiditys

110     function getAverages(
111       uint32 time,
112       int24 tick,
113       uint16 index,
114       uint128 liquidity
115:    ) external view override onlyPool returns (uint112 TWVolatilityAverage, uint256 TWVolumePerLiqAverage) {

120     function write(
121       uint16 index,
122       uint32 blockTimestamp,
123       int24 tick,
124       uint128 liquidity,
125       uint128 volumePerLiquidity
126:    ) external override onlyPool returns (uint16 indexUpdated) {

150     function getFee(
151       uint32 _time,
152       int24 _tick,
153       uint16 _index,
154       uint128 _liquidity
155:    ) external view override onlyPool returns (uint16 fee) {

https://github.com/code-423n4/2022-09-quickswap/blob/15ea643c85ed936a92d2676a7aabf739b210af39/src/core/contracts/DataStorageOperator.sol#L37

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