Platform: Code4rena
Start Date: 01/12/2022
Pot Size: $60,500 USDC
Total HM: 8
Participants: 27
Period: 11 days
Judge: kirk-baird
Total Solo HM: 6
Id: 187
League: ETH
Rank: 7/27
Findings: 2
Award: $1,359.35
š Selected for report: 1
š Solo Findings: 0
š Selected for report: 0xSmartContract
Also found by: 0x1f8b, 0xDecorativePineapple, Chom, Deivitto, IllIllI, Jeiwan, Josiah, Mukund, RaymondFam, Rolezn, ajtra, cccz, chrisdior4, csanuragjain, hansfriese, ladboy233, minhquanym, pedr02b2, peritoflores, rvierdiiev, sakshamguruji, sces60107
728.8818 USDC - $728.88
Issue | Instances | |
---|---|---|
[Lā01] | tokenURI() does not follow EIP-721 | 1 |
[Lā02] | Draft imports may break in new minor versions | 1 |
Total: 2 instances over 2 issues
Issue | Instances | |
---|---|---|
[Nā01] | Import declarations should import specific identifiers, rather than the whole file | 97 |
[Nā02] | constant s should be defined rather than using magic numbers | 107 |
[Nā03] | Numeric values having to do with time should use time units for readability | 1 |
[Nā04] | Use bit shifts in an imutable variable rather than long bit masks of a single bit, for readability | 4 |
[Nā05] | Missing event and or timelock for critical parameter change | 1 |
[Nā06] | Events that mark critical parameter changes should contain both the old and the new value | 3 |
[Nā07] | Use a more recent version of solidity | 1 |
[Nā08] | Use a more recent version of solidity | 8 |
[Nā09] | Use a more recent version of solidity | 1 |
[Nā10] | Inconsistent spacing in comments | 28 |
[Nā11] | Lines are too long | 18 |
[Nā12] | Variable names that consist of all capital letters should be reserved for constant /immutable variables | 1 |
[Nā13] | Non-library/interface files should use fixed compiler versions, not floating ones | 6 |
[Nā14] | File is missing NatSpec | 18 |
[Nā15] | NatSpec is incomplete | 9 |
[Nā16] | Not using the named return variables anywhere in the function is confusing | 17 |
[Nā17] | Duplicated require() /revert() checks should be refactored to a modifier or function | 3 |
[Nā18] | Consider using delete rather than assigning zero to clear values | 6 |
[Nā19] | Contracts should have full test coverage | 1 |
[Nā20] | Large or complicated code bases should implement fuzzing tests | 1 |
Total: 331 instances over 20 issues
tokenURI()
does not follow EIP-721The EIP states that tokenURI()
"Throws if _tokenId
is not a valid NFT", which the code below does not do. If the NFT has not yet been minted, tokenURI()
should revert
There is 1 instance of this issue:
File: maverick-v1/contracts/models/PositionMetadata.sol 21 function tokenURI(uint256 tokenId) external view override returns (string memory) { 22 return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; 23: }
While OpenZeppelin draft contracts are safe to use and have been audited, their 'draft' status means that the EIPs they're based on are not finalized, and thus there may be breaking changes in even minor releases. If a bug is found in this version of OpenZeppelin, and the version that you're forced to upgrade to has breaking changes in the new version, you'll encounter unnecessary delays in porting and testing replacement contracts. Ensure that you have extensive test coverage of this area so that differences can be automatically detected, and have a plan in place for how you would fully test a new version of these contracts if they do indeed change unexpectedly. Consider creating a forked version of the file rather than importing it from the package, and manually patch your fork as changes are made.
There is 1 instance of this issue:
File: router-v1/contracts/libraries/SelfPermit.sol 5: import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
Using import declarations of the form import {<identifier_name>} from "some/file.sol"
avoids polluting the symbol namespace making flattened files smaller, and speeds up compilation
There are 97 instances of this issue:
File: maverick-v1/contracts/interfaces/IFactory.sol 3: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5: import "../interfaces/IPool.sol"; 6: import "../interfaces/IPosition.sol";
File: maverick-v1/contracts/interfaces/IPool.sol 4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5: import "./IFactory.sol";
File: maverick-v1/contracts/interfaces/IPosition.sol 4: import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; 5: import "../interfaces/IPositionMetadata.sol";
File: maverick-v1/contracts/libraries/BinMap.sol 4: import "prb-math/contracts/PRBMathSD59x18.sol"; 6: import "./BinMath.sol"; 7: import "./Constants.sol";
File: maverick-v1/contracts/libraries/BinMath.sol 4: import "prb-math/contracts/PRBMathUD60x18.sol"; 6: import "./Math.sol"; 7: import "./Constants.sol";
File: maverick-v1/contracts/libraries/Bin.sol 4: import "prb-math/contracts/PRBMathUD60x18.sol"; 6: import "./Math.sol"; 7: import "./BinMath.sol"; 8: import "./Delta.sol"; 9: import "./Cast.sol"; 10: import "./Constants.sol"; 11: import "../interfaces/IPool.sol";
File: maverick-v1/contracts/libraries/Deployer.sol 4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6: import "../models/Pool.sol"; 7: import "../interfaces/IFactory.sol"; 8: import "../interfaces/IPool.sol"; 9: import "../interfaces/IPosition.sol";
File: maverick-v1/contracts/libraries/Math.sol 3: import "prb-math/contracts/PRBMath.sol"; 5: import "./Constants.sol";
File: maverick-v1/contracts/libraries/SafeERC20Min.sol 5: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6: import "@openzeppelin/contracts/utils/Address.sol";
File: maverick-v1/contracts/libraries/Twa.sol 4: import "prb-math/contracts/PRBMathSD59x18.sol"; 6: import "../interfaces/IPool.sol";
File: maverick-v1/contracts/models/Factory.sol 4: import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 5: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7: import "../models/Pool.sol"; 8: import "../interfaces/IFactory.sol"; 9: import "../interfaces/IPool.sol"; 10: import "../interfaces/IPosition.sol"; 11: import "../libraries/Deployer.sol"; 12: import "../libraries/Math.sol";
File: maverick-v1/contracts/models/PoolInspector.sol 4: import "./Pool.sol"; 5: import "../interfaces/ISwapCallback.sol"; 6: import "../libraries/Bin.sol"; 7: import "../libraries/Math.sol"; 8: import "../libraries/Constants.sol"; 9: import "../interfaces/IPool.sol";
File: maverick-v1/contracts/models/Pool.sol 4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6: import "prb-math/contracts/PRBMathUD60x18.sol"; 7: import "prb-math/contracts/PRBMathSD59x18.sol"; 8: import "../libraries/Bin.sol"; 9: import "../libraries/BinMap.sol"; 10: import "../libraries/BinMath.sol"; 11: import "../libraries/Cast.sol"; 12: import "../libraries/Delta.sol"; 13: import "../libraries/Twa.sol"; 14: import "../libraries/SafeERC20Min.sol"; 15: import "../interfaces/ISwapCallback.sol"; 16: import "../interfaces/IAddLiquidityCallback.sol"; 17: import "../interfaces/IPool.sol"; 18: import "../interfaces/IPoolAdmin.sol"; 19: import "../interfaces/IFactory.sol"; 20: import "../interfaces/IPosition.sol";
File: maverick-v1/contracts/models/PositionMetadata.sol 4: import "../interfaces/IPositionMetadata.sol"; 5: import "@openzeppelin/contracts/utils/Strings.sol"; 6: import "@openzeppelin/contracts/access/Ownable.sol";
File: maverick-v1/contracts/models/Position.sol 4: import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5: import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 6: import "@openzeppelin/contracts/utils/Counters.sol"; 7: import "@openzeppelin/contracts/access/Ownable.sol"; 9: import "../interfaces/IPositionMetadata.sol"; 10: import "../interfaces/IPosition.sol";
File: router-v1/contracts/interfaces/external/IWETH9.sol 4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
File: router-v1/contracts/interfaces/IRouter.sol 3: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5: import "@maverick/contracts/contracts/interfaces/IFactory.sol"; 6: import "@maverick/contracts/contracts/interfaces/IPool.sol"; 7: import "@maverick/contracts/contracts/interfaces/IPosition.sol"; 8: import "@maverick/contracts/contracts/interfaces/ISwapCallback.sol"; 9: import "./external/IWETH9.sol";
File: router-v1/contracts/libraries/Multicall.sol 5: import "../interfaces/IMulticall.sol";
File: router-v1/contracts/libraries/Path.sol 4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6: import "@maverick/contracts/contracts/interfaces/IPool.sol"; 8: import "./BytesLib.sol";
File: router-v1/contracts/libraries/SelfPermit.sol 4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5: import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol"; 7: import "../interfaces/ISelfPermit.sol"; 8: import "../interfaces/external/IERC20PermitAllowed.sol";
File: router-v1/contracts/libraries/TransferHelper.sol 4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
File: router-v1/contracts/Router.sol 4: import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6: import "@maverick/contracts/contracts/interfaces/IPool.sol"; 7: import "@maverick/contracts/contracts/interfaces/IFactory.sol"; 8: import "@maverick/contracts/contracts/interfaces/IPosition.sol"; 10: import "./interfaces/IRouter.sol"; 11: import "./interfaces/external/IWETH9.sol"; 12: import "./libraries/TransferHelper.sol"; 13: import "./libraries/Path.sol"; 14: import "./libraries/Deadline.sol"; 15: import "./libraries/Multicall.sol"; 16: import "./libraries/SelfPermit.sol";
constant
s should be defined rather than using magic numbersEven assembly can benefit from using readable constants instead of hex/numeric literals
There are 107 instances of this issue:
File: maverick-v1/contracts/libraries/BinMap.sol /// @audit 8 22: mapIndex = tick_kinds >> 8;
File: maverick-v1/contracts/libraries/BinMath.sol /// @audit 0x100000000000000000000000000000000 16: if (x >= 0x100000000000000000000000000000000) { /// @audit 128 17: x >>= 128; /// @audit 128 18: result += 128; /// @audit 0x10000000000000000 20: if (x >= 0x10000000000000000) { /// @audit 64 21: x >>= 64; /// @audit 64 22: result += 64; /// @audit 0x100000000 24: if (x >= 0x100000000) { /// @audit 32 25: x >>= 32; /// @audit 32 26: result += 32; /// @audit 0x10000 28: if (x >= 0x10000) { /// @audit 16 29: x >>= 16; /// @audit 16 30: result += 16; /// @audit 0x100 32: if (x >= 0x100) { /// @audit 8 33: x >>= 8; /// @audit 8 34: result += 8; /// @audit 0x10 36: if (x >= 0x10) { /// @audit 4 37: x >>= 4; /// @audit 4 38: result += 4; /// @audit 0x4 40: if (x >= 0x4) { /// @audit 0x2 44: if (x >= 0x2) result += 1; /// @audit 255 50: result = 255; /// @audit 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 52: if (x & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF > 0) { /// @audit 128 53: result -= 128; /// @audit 128 55: x >>= 128; /// @audit 0xFFFFFFFFFFFFFFFF 57: if (x & 0xFFFFFFFFFFFFFFFF > 0) { /// @audit 64 58: result -= 64; /// @audit 64 60: x >>= 64; /// @audit 0xFFFFFFFF 62: if (x & 0xFFFFFFFF > 0) { /// @audit 32 63: result -= 32; /// @audit 32 65: x >>= 32; /// @audit 0xFFFF 67: if (x & 0xFFFF > 0) { /// @audit 16 68: result -= 16; /// @audit 16 70: x >>= 16; /// @audit 0xFF 72: if (x & 0xFF > 0) { /// @audit 8 73: result -= 8; /// @audit 8 75: x >>= 8; /// @audit 0xF 77: if (x & 0xF > 0) { /// @audit 4 78: result -= 4; /// @audit 4 80: x >>= 4; /// @audit 0x3 82: if (x & 0x3 > 0) { /// @audit 0x1 87: if (x & 0x1 > 0) result -= 1; /// @audit 0x1 /// @audit 0xfffcb933bd6fad9d3af5f0b9f25db4d6 /// @audit 0x100000000000000000000000000000000 96: uint256 ratio = tick & 0x1 != 0 ? 0xfffcb933bd6fad9d3af5f0b9f25db4d6 : 0x100000000000000000000000000000000; /// @audit 0x2 /// @audit 0xfff97272373d41fd789c8cb37ffcaa1c /// @audit 128 97: if (tick & 0x2 != 0) ratio = (ratio * 0xfff97272373d41fd789c8cb37ffcaa1c) >> 128; /// @audit 0x4 /// @audit 0xfff2e50f5f656ac9229c67059486f389 /// @audit 128 98: if (tick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656ac9229c67059486f389) >> 128; /// @audit 0x8 /// @audit 0xffe5caca7e10e81259b3cddc7a064941 /// @audit 128 99: if (tick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e81259b3cddc7a064941) >> 128; /// @audit 0x10 /// @audit 0xffcb9843d60f67b19e8887e0bd251eb7 /// @audit 128 100: if (tick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f67b19e8887e0bd251eb7) >> 128; /// @audit 0x20 /// @audit 0xff973b41fa98cd2e57b660be99eb2c4a /// @audit 128 101: if (tick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98cd2e57b660be99eb2c4a) >> 128; /// @audit 0x40 /// @audit 0xff2ea16466c9838804e327cb417cafcb /// @audit 128 102: if (tick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c9838804e327cb417cafcb) >> 128; /// @audit 0x80 /// @audit 0xfe5dee046a99d51e2cc356c2f617dbe0 /// @audit 128 103: if (tick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99d51e2cc356c2f617dbe0) >> 128; /// @audit 0x100 /// @audit 0xfcbe86c7900aecf64236ab31f1f9dcb5 /// @audit 128 104: if (tick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900aecf64236ab31f1f9dcb5) >> 128; /// @audit 0x200 /// @audit 0xf987a7253ac4d9194200696907cf2e37 /// @audit 128 105: if (tick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac4d9194200696907cf2e37) >> 128; /// @audit 0x400 /// @audit 0xf3392b0822b88206f8abe8a3b44dd9be /// @audit 128 106: if (tick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b88206f8abe8a3b44dd9be) >> 128; /// @audit 0x800 /// @audit 0xe7159475a2c578ef4f1d17b2b235d480 /// @audit 128 107: if (tick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c578ef4f1d17b2b235d480) >> 128; /// @audit 0x1000 /// @audit 0xd097f3bdfd254ee83bdd3f248e7e785e /// @audit 128 108: if (tick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd254ee83bdd3f248e7e785e) >> 128; /// @audit 0x2000 /// @audit 0xa9f746462d8f7dd10e744d913d033333 /// @audit 128 109: if (tick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d8f7dd10e744d913d033333) >> 128; /// @audit 0x4000 /// @audit 0x70d869a156ddd32a39e257bc3f50aa9b /// @audit 128 110: if (tick & 0x4000 != 0) ratio = (ratio * 0x70d869a156ddd32a39e257bc3f50aa9b) >> 128; /// @audit 0x8000 /// @audit 0x31be135f97da6e09a19dc367e3b6da40 /// @audit 128 111: if (tick & 0x8000 != 0) ratio = (ratio * 0x31be135f97da6e09a19dc367e3b6da40) >> 128; /// @audit 0x10000 /// @audit 0x9aa508b5b7e5a9780b0cc4e25d61a56 /// @audit 128 112: if (tick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7e5a9780b0cc4e25d61a56) >> 128; /// @audit 0x20000 /// @audit 0x5d6af8dedbcb3a6ccb7ce618d14225 /// @audit 128 113: if (tick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedbcb3a6ccb7ce618d14225) >> 128; /// @audit 0x40000 /// @audit 0x2216e584f630389b2052b8db590e /// @audit 128 114: if (tick & 0x40000 != 0) ratio = (ratio * 0x2216e584f630389b2052b8db590e) >> 128; /// @audit 128 116: uint256 result = (ratio * PRBMathUD60x18.SCALE) >> 128; /// @audit 4 127: b + (b.mul(b) + Math.mulDiv(4 * _reserveB.mul(_reserveA), _sqrtUpperTickPrice - _sqrtLowerTickPrice, _sqrtUpperTickPrice, false)).sqrt(),
File: maverick-v1/contracts/models/Factory.sol /// @audit 1e18 60: require(_fee > 0 && _fee < 1e18, "Factory:FEE_OUT_OF_BOUNDS"); /// @audit 3600 62: require(_lookback >= 3600 && _lookback <= uint16(type(int16).max), "Factory:LOOKBACK_OUT_OF_BOUNDS");
File: maverick-v1/contracts/models/Pool.sol /// @audit 0x1111111111111111111111111111111111111111111111111111111111111111 462: 0x1111111111111111111111111111111111111111111111111111111111111111 * (KIND_RIGHT | KIND_BOTH) /// @audit 0x1111111111111111111111111111111111111111111111111111111111111111 479: 0x1111111111111111111111111111111111111111111111111111111111111111 * (KIND_LEFT | KIND_BOTH)
File: router-v1/contracts/libraries/Multicall.sol /// @audit 68 18: if (result.length < 68) revert(); /// @audit 0x04 20: result := add(result, 0x04)
There are units for seconds, minutes, hours, days, and weeks, and since they're defined, they should be used
There is 1 instance of this issue:
File: maverick-v1/contracts/models/Factory.sol /// @audit 3600 62: require(_lookback >= 3600 && _lookback <= uint16(type(int16).max), "Factory:LOOKBACK_OUT_OF_BOUNDS");
There are 4 instances of this issue:
File: maverick-v1/contracts/libraries/BinMath.sol 16: if (x >= 0x100000000000000000000000000000000) { 20: if (x >= 0x10000000000000000) { 24: if (x >= 0x100000000) { 96: uint256 ratio = tick & 0x1 != 0 ? 0xfffcb933bd6fad9d3af5f0b9f25db4d6 : 0x100000000000000000000000000000000;
Events help non-contract tools to track changes, and events prevent users from being surprised by changes
There is 1 instance of this issue:
File: maverick-v1/contracts/models/PositionMetadata.sol 17 function setBaseURI(string memory _baseURI) external onlyOwner { 18 baseURI = _baseURI; 19: }
This should especially be done if the new value is not required to be different from the old value
There are 3 instances of this issue:
File: maverick-v1/contracts/models/Factory.sol /// @audit setProtocolFeeRatio() 37: emit SetFactoryProtocolFeeRatio(_protocolFeeRatio); /// @audit setOwner() 43: emit SetFactoryOwner(_owner);
File: maverick-v1/contracts/models/Position.sol /// @audit setMetadata() 25: emit SetMetadata(metadata);
Use a solidity version of at least 0.8.12 to get string.concat()
to be used instead of abi.encodePacked(<str>,<str>)
There is 1 instance of this issue:
File: maverick-v1/contracts/models/PositionMetadata.sol 2: pragma solidity ^0.8.4;
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 8 instances of this issue:
File: maverick-v1/contracts/libraries/BinMath.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/Bin.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/SafeERC20Min.sol 3: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/Twa.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/models/PoolInspector.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/models/Pool.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/models/Position.sol 2: pragma solidity ^0.8.4;
File: router-v1/contracts/libraries/Path.sol 2: pragma solidity ^0.8.0;
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: router-v1/contracts/Router.sol 2: pragma solidity ^0.8.0;
Some lines use // x
and some use //x
. The instances below point out the usages that don't follow the majority, within each file
There are 28 instances of this issue:
File: maverick-v1/contracts/interfaces/IFactory.sol 29: //protocol
File: maverick-v1/contracts/interfaces/IPoolAdmin.sol 7: //or B protocol fee (2 or 3)
File: maverick-v1/contracts/interfaces/IPool.sol 78: //to the current bin or an absolute position 100: //defined in Pool.sol 103: //protocol 147: //position of the liquidity 149: //caller can transfer tokens 153: //to another 163: //and amounts 167: //mergeId is the currrent active bin. 170: //zero to recurse until the active bin is found. 176: //is false or the output if exactOutput is true 179: //exact output amount (true) 181: //indicates no limit. Limit is only engaged for exactOutput=false. If the 182: //limit is reached only part of the input amount will be swapped and the 183: //callback will only require that amount of the swap to be paid. 185: //caller can transfer tokens
File: maverick-v1/contracts/models/Pool.sol 356: //********************** Internal **********************/
File: router-v1/contracts/interfaces/IRouter.sol 33: //another token 35: //`ExactInputSingleParams` in calldata 48: //another along the specified path 50: //as `ExactInputParams` in calldata 65: //another token 67: //`ExactOutputSingleParams` in calldata 80: //another along the specified path (reversed) 82: //as `ExactOutputParams` in calldata
File: router-v1/contracts/Router.sol 21: //computed amount in for an exact output swap / can never actually be this 22: //value
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 are 18 instances of this issue:
File: maverick-v1/contracts/interfaces/IFactory.sol 9: event PoolCreated(address poolAddress, uint256 fee, uint256 tickSpacing, int32 activeTick, uint32 lookback, uint16 protocolFeeRatio, IERC20 tokenA, IERC20 tokenB);
File: maverick-v1/contracts/interfaces/IPool.sol 150: function addLiquidity(uint256 tokenId, AddLiquidityParams[] calldata params, bytes calldata data) external returns (uint256 tokenAAmount, uint256 tokenBAmount, BinDelta[] memory binDeltas); 164: function removeLiquidity(address recipient, uint256 tokenId, RemoveLiquidityParams[] calldata params) external returns (uint256 tokenAOut, uint256 tokenBOut, BinDelta[] memory binDeltas); 186: function swap(address recipient, uint256 amount, bool tokenAIn, bool exactOutput, uint256 sqrtPriceLimit, bytes calldata data) external returns (uint256 amountIn, uint256 amountOut);
File: maverick-v1/contracts/libraries/BinMap.sol 41: function getKindsAtTickRange(mapping(int32 => uint256) storage binMap, int32 startTick, int32 endTick, uint256 kindMask) internal view returns (Active[] memory activeList, uint256 counter) {
File: maverick-v1/contracts/libraries/BinMath.sol 134: function getTickSqrtPriceAndL(uint256 _reserveA, uint256 _reserveB, uint256 _sqrtLowerTickPrice, uint256 _sqrtUpperTickPrice) internal pure returns (uint256 sqrtPrice, uint256 liquidity) {
File: maverick-v1/contracts/libraries/Bin.sol 52: : Math.min(Math.mulDiv(deltaAOptimal, self.state.totalSupply, self.state.reserveA, false), Math.mulDiv(deltaBOptimal, self.state.totalSupply, self.state.reserveB, false));
File: maverick-v1/contracts/models/Factory.sol 58: function create(uint256 _fee, uint256 _tickSpacing, uint16 _lookback, int32 _activeTick, IERC20 _tokenA, IERC20 _tokenB) external override returns (IPool pool) {
File: maverick-v1/contracts/models/PoolInspector.sol 33: bins[activeCounter] = BinInfo({id: i + 1, kind: bin.kind, lowerTick: bin.lowerTick, reserveA: bin.reserveA, reserveB: bin.reserveB, mergeId: bin.mergeId}); 107: (sqrtPrice, liquidity) = BinMath.getTickSqrtPriceAndL(reserveA, reserveB, BinMath.tickSqrtPrice(tickSpacing, activeTick), BinMath.tickSqrtPrice(tickSpacing, activeTick + 1));
File: maverick-v1/contracts/models/Pool.sol 238: binDeltas[i] = BinDelta({binId: param.binId, deltaA: deltaA, deltaB: deltaB, kind: bin.state.kind, isActive: isActive, lowerTick: bin.state.lowerTick, deltaLpBalance: deltaLpBalance}); 260: function swap(address recipient, uint256 amount, bool tokenAIn, bool exactOutput, uint256 sqrtPriceLimit, bytes calldata data) external returns (uint256 amountIn, uint256 amountOut) { 270: delta.excess = (!exactOutput) ? Math.fromScale(amount, tokenAIn ? tokenAScale : tokenBScale) : Math.fromScale(amount, !tokenAIn ? tokenAScale : tokenBScale); 294: twa.updateValue(currentState.activeTick * PRBMathSD59x18.SCALE + int256(Math.clip(delta.endSqrtPrice, delta.sqrtLowerTickPrice).div(delta.sqrtUpperTickPrice - delta.sqrtLowerTickPrice))); 516: function _currentTickLiquidity(int32 tick) internal view returns (uint256 sqrtLowerTickPrice, uint256 sqrtUpperTickPrice, uint256 sqrtPrice, uint128[] memory binIds, SwapData memory output) { 597: function _computeSwapExactOut(uint256 sqrtPrice, uint256 liquidity, uint256 reserveA, uint256 reserveB, uint256 amountOut, bool tokenAIn) internal view returns (Delta.Instance memory delta) { 602: binAmountIn = Math.mulDiv(delta.deltaOutErc, tokenAIn ? sqrtPrice : sqrtPrice.inv(), (tokenAIn ? sqrtPrice.inv() : sqrtPrice) - delta.deltaOutErc.div(liquidity), true);
File: router-v1/contracts/Router.sol 272: pool = IFactory(factory).create(poolParams.fee, poolParams.tickSpacing, poolParams.lookback, poolParams.activeTick, poolParams.tokenA, poolParams.tokenB);
constant
/immutable
variablesIf 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 is 1 instance of this issue:
File: router-v1/contracts/Router.sol 48: constructor(IFactory _factory, IPosition _position, IWETH9 _WETH9) {
There are 6 instances of this issue:
File: maverick-v1/contracts/models/Factory.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/models/PoolInspector.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/models/Pool.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/models/PositionMetadata.sol 2: pragma solidity ^0.8.4;
File: maverick-v1/contracts/models/Position.sol 2: pragma solidity ^0.8.4;
File: router-v1/contracts/Router.sol 2: pragma solidity ^0.8.0;
There are 18 instances of this issue:
File: maverick-v1/contracts/interfaces/IAddLiquidityCallback.sol
File: maverick-v1/contracts/interfaces/IPositionMetadata.sol
File: maverick-v1/contracts/interfaces/ISwapCallback.sol
File: maverick-v1/contracts/libraries/BinMap.sol
File: maverick-v1/contracts/libraries/BinMath.sol
File: maverick-v1/contracts/libraries/Bin.sol
File: maverick-v1/contracts/libraries/Cast.sol
File: maverick-v1/contracts/libraries/Constants.sol
File: maverick-v1/contracts/libraries/Delta.sol
File: maverick-v1/contracts/libraries/Deployer.sol
File: maverick-v1/contracts/libraries/Math.sol
File: maverick-v1/contracts/libraries/SafeERC20Min.sol
File: maverick-v1/contracts/libraries/Twa.sol
File: maverick-v1/contracts/models/PoolInspector.sol
File: maverick-v1/contracts/models/Pool.sol
File: maverick-v1/contracts/models/PositionMetadata.sol
File: maverick-v1/contracts/models/Position.sol
File: router-v1/contracts/libraries/Deadline.sol
There are 9 instances of this issue:
File: maverick-v1/contracts/interfaces/IFactory.sol /// @audit Missing: '@return' 18 /// @param _tokenA ERC20 token 19 /// @param _tokenB ERC20 token 20: function create(uint256 _fee, uint256 _tickSpacing, uint16 _lookback, int32 _activeTick, IERC20 _tokenA, IERC20 _tokenB) external returns (IPool);
File: maverick-v1/contracts/interfaces/IPool.sol /// @audit Missing: '@return' 148 /// @param data callback function that addLiquidity will call so that the 149 //caller can transfer tokens 150: function addLiquidity(uint256 tokenId, AddLiquidityParams[] calldata params, bytes calldata data) external returns (uint256 tokenAAmount, uint256 tokenBAmount, BinDelta[] memory binDeltas); /// @audit Missing: '@return' 162 /// @param params array of RemoveLiquidityParams that specify the bins, 163 //and amounts 164: function removeLiquidity(address recipient, uint256 tokenId, RemoveLiquidityParams[] calldata params) external returns (uint256 tokenAOut, uint256 tokenBOut, BinDelta[] memory binDeltas); /// @audit Missing: '@return' 184 /// @param data callback function that swap will call so that the 185 //caller can transfer tokens 186: function swap(address recipient, uint256 amount, bool tokenAIn, bool exactOutput, uint256 sqrtPriceLimit, bytes calldata data) external returns (uint256 amountIn, uint256 amountOut);
File: maverick-v1/contracts/models/Factory.sol /// @audit Missing: '@return' 56 /// @param _tokenA ERC20 token 57 /// @param _tokenB ERC20 token 58: function create(uint256 _fee, uint256 _tickSpacing, uint16 _lookback, int32 _activeTick, IERC20 _tokenA, IERC20 _tokenB) external override returns (IPool pool) {
File: router-v1/contracts/interfaces/IRouter.sol /// @audit Missing: '@return' 118 /// @param minTokenBAmount minimum amount of token B to add, revert if not met 119 /// @param deadline epoch timestamp in seconds 120 function getOrCreatePoolAndAddLiquidity( 121 PoolParams calldata poolParams, 122 uint256 tokenId, 123 IPool.AddLiquidityParams[] calldata addParams, 124 uint256 minTokenAAmount, 125 uint256 minTokenBAmount, 126 uint256 deadline 127: ) external payable returns (uint256 receivingTokenId, uint256 tokenAAmount, uint256 tokenBAmount, IPool.BinDelta[] memory binDeltas); /// @audit Missing: '@return' 134 /// @param minTokenBAmount minimum amount of token B to add, revert if not met 135 /// @param deadline epoch timestamp in seconds 136 function addLiquidityToPool( 137 IPool pool, 138 uint256 tokenId, 139 IPool.AddLiquidityParams[] calldata params, 140 uint256 minTokenAAmount, 141 uint256 minTokenBAmount, 142 uint256 deadline 143: ) external payable returns (uint256 receivingTokenId, uint256 tokenAAmount, uint256 tokenBAmount, IPool.BinDelta[] memory binDeltas); /// @audit Missing: '@return' 152 /// @param maxActiveTick highest activeTick (inclusive) of pool that will permit transaction to pass 153 /// @param deadline epoch timestamp in seconds 154 function addLiquidityWTickLimits( 155 IPool pool, 156 uint256 tokenId, 157 IPool.AddLiquidityParams[] calldata params, 158 uint256 minTokenAAmount, 159 uint256 minTokenBAmount, 160 int32 minActiveTick, 161 int32 maxActiveTick, 162 uint256 deadline 163: ) external payable returns (uint256 receivingTokenId, uint256 tokenAAmount, uint256 tokenBAmount, IPool.BinDelta[] memory binDeltas); /// @audit Missing: '@return' 179 /// @param minTokenBAmount minimum amount of token B to receive, revert if not met 180 /// @param deadline epoch timestamp in seconds 181 function removeLiquidity( 182 IPool pool, 183 address recipient, 184 uint256 tokenId, 185 IPool.RemoveLiquidityParams[] calldata params, 186 uint256 minTokenAAmount, 187 uint256 minTokenBAmount, 188 uint256 deadline 189: ) external returns (uint256 tokenAAmount, uint256 tokenBAmount, IPool.BinDelta[] memory binDeltas);
Consider changing the variable to be an unnamed one
There are 17 instances of this issue:
File: maverick-v1/contracts/libraries/BinMath.sol /// @audit _result 91: function tickSqrtPrice(uint256 tickSpacing, int32 _tick) internal pure returns (uint256 _result) {
File: maverick-v1/contracts/libraries/Delta.sol /// @audit edge 38: function sqrtEdgePrice(Instance memory self) internal pure returns (uint256 edge) {
File: maverick-v1/contracts/models/Factory.sol /// @audit pool 46: function lookup(uint256 _fee, uint256 _tickSpacing, uint16 _lookback, IERC20 _tokenA, IERC20 _tokenB) external view override returns (IPool pool) {
File: maverick-v1/contracts/models/Pool.sol /// @audit bin 312: function getBin(uint128 binId) external view override returns (BinState memory bin) { /// @audit lpToken 316: function balanceOf(uint256 tokenId, uint128 binId) external view override returns (uint256 lpToken) {
File: router-v1/contracts/Router.sol /// @audit receivingTokenId /// @audit tokenAAmount /// @audit tokenBAmount /// @audit binDeltas 238 function addLiquidityToPool( 239 IPool pool, 240 uint256 tokenId, 241 IPool.AddLiquidityParams[] calldata params, 242 uint256 minTokenAAmount, 243 uint256 minTokenBAmount, 244 uint256 deadline 245: ) external payable checkDeadline(deadline) returns (uint256 receivingTokenId, uint256 tokenAAmount, uint256 tokenBAmount, IPool.BinDelta[] memory binDeltas) { /// @audit receivingTokenId /// @audit tokenAAmount /// @audit tokenBAmount /// @audit binDeltas 250 function addLiquidityWTickLimits( 251 IPool pool, 252 uint256 tokenId, 253 IPool.AddLiquidityParams[] calldata params, 254 uint256 minTokenAAmount, 255 uint256 minTokenBAmount, 256 int32 minActiveTick, 257 int32 maxActiveTick, 258 uint256 deadline 259: ) external payable checkDeadline(deadline) returns (uint256 receivingTokenId, uint256 tokenAAmount, uint256 tokenBAmount, IPool.BinDelta[] memory binDeltas) { /// @audit receivingTokenId /// @audit tokenAAmount /// @audit tokenBAmount /// @audit binDeltas 277 function getOrCreatePoolAndAddLiquidity( 278 PoolParams calldata poolParams, 279 uint256 tokenId, 280 IPool.AddLiquidityParams[] calldata addParams, 281 uint256 minTokenAAmount, 282 uint256 minTokenBAmount, 283 uint256 deadline 284: ) external payable checkDeadline(deadline) returns (uint256 receivingTokenId, uint256 tokenAAmount, uint256 tokenBAmount, IPool.BinDelta[] memory binDeltas) {
require()
/revert()
checks should be refactored to a modifier or functionThe compiler will inline the function, which will avoid JUMP
instructions usually associated with functions
There are 3 instances of this issue:
File: maverick-v1/contracts/models/Factory.sol 35: require(_protocolFeeRatio <= ONE_3_DECIMAL_SCALE, "Factory:PROTOCOL_FEE_CANNOT_EXCEED_ONE");
File: router-v1/contracts/Router.sol 165: require(amountOut >= params.amountOutMinimum, "Too little received"); 197: require(amountIn <= params.amountInMaximum, "Too much requested");
delete
rather than assigning zero to clear valuesThe delete
keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic
There are 6 instances of this issue:
File: maverick-v1/contracts/libraries/Delta.sol 43: self.excess = 0;
File: maverick-v1/contracts/models/Pool.sol 359: binPositions[tick][kind] = 0; 382: moveData.firstBinId = 0; 384: moveData.mergeBinCounter = 0; 385: moveData.totalReserveA = 0; 386: moveData.totalReserveB = 0;
While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.
There is 1 instance of this issue:
File: maverick-v1/contracts/interfaces/IAddLiquidityCallback.sol
Large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts, should implement fuzzing tests. Fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and fuzzers, with properly and extensively-written invariants, can close this testing gap significantly.
There is 1 instance of this issue:
File: maverick-v1/contracts/interfaces/IAddLiquidityCallback.sol
These findings are excluded from awards calculations because there are publicly-available automated tools that find them. The valid ones appear here for completeness
Issue | Instances | |
---|---|---|
[Nā01] | require() /revert() statements should have descriptive reason strings | 7 |
[Nā02] | public functions not called by the contract should be declared external instead | 2 |
[Nā03] | Event is missing indexed fields | 12 |
Total: 21 instances over 3 issues
require()
/revert()
statements should have descriptive reason stringsThere are 7 instances of this issue:
File: maverick-v1/contracts/models/Pool.sol /// @audit (valid but excluded finding) 129: require(param.kind < NUMBER_OF_KINDS); /// @audit (valid but excluded finding) 333: require(_protocolFeeRatio <= ONE_3_DECIMAL_SCALE); /// @audit (valid but excluded finding) 342: require(msg.sender == factory.owner());
File: router-v1/contracts/libraries/Multicall.sol /// @audit (valid but excluded finding) 18: if (result.length < 68) revert();
File: router-v1/contracts/Router.sol /// @audit (valid but excluded finding) 106: require(msg.sender == address(pool)); /// @audit (valid but excluded finding) 205: require(factory.isFactoryPool(IPool(msg.sender))); /// @audit (valid but excluded finding) 206: require(msg.sender == address(data.pool));
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents' functions and change the visibility from external
to public
.
There are 2 instances of this issue:
File: maverick-v1/contracts/models/PoolInspector.sol /// @audit (valid but excluded finding) 45: function getBinDepth(IPool pool, uint128 binId) public view returns (uint256 depth) {
File: router-v1/contracts/Router.sol /// @audit (valid but excluded finding) 70: function sweepToken(IERC20 token, uint256 amountMinimum, address recipient) public payable {
indexed
fieldsIndex event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Each event
should use three indexed
fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.
There are 12 instances of this issue:
File: maverick-v1/contracts/interfaces/IFactory.sol /// @audit (valid but excluded finding) 9: event PoolCreated(address poolAddress, uint256 fee, uint256 tickSpacing, int32 activeTick, uint32 lookback, uint16 protocolFeeRatio, IERC20 tokenA, IERC20 tokenB); /// @audit (valid but excluded finding) 10: event SetFactoryProtocolFeeRatio(uint16 protocolFeeRatio); /// @audit (valid but excluded finding) 11: event SetFactoryOwner(address owner);
File: maverick-v1/contracts/interfaces/IPool.sol /// @audit (valid but excluded finding) 8: event Swap(address indexed sender, address indexed recipient, bool tokenAIn, bool exactOutput, uint256 amountIn, uint256 amountOut, int32 activeTick); /// @audit (valid but excluded finding) 10: event AddLiquidity(address indexed sender, uint256 indexed tokenId, BinDelta[] binDeltas); /// @audit (valid but excluded finding) 12: event MigrateBinsUpStack(address indexed sender, uint128[] binIds, uint32 maxRecursion); /// @audit (valid but excluded finding) 14: event TransferLiquidity(uint256 fromTokenId, uint256 toTokenId, RemoveLiquidityParams[] params); /// @audit (valid but excluded finding) 18: event BinMerged(uint128 indexed binId, uint128 reserveA, uint128 reserveB, uint128 mergeId); /// @audit (valid but excluded finding) 20: event BinMoved(uint128 indexed binId, int128 previousTick, int128 newTick); /// @audit (valid but excluded finding) 22: event ProtocolFeeCollected(uint256 protocolFee, bool isTokenA); /// @audit (valid but excluded finding) 24: event SetProtocolFeeRatio(uint256 protocolFee);
File: maverick-v1/contracts/interfaces/IPosition.sol /// @audit (valid but excluded finding) 8: event SetMetadata(IPositionMetadata metadata);
#0 - c4-judge
2023-01-18T22:12:56Z
kirk-baird marked the issue as grade-a
š Selected for report: IllIllI
Also found by: 0x1f8b, 0xSmartContract, Deivitto, Mukund, RaymondFam, Rolezn, ajtra, c3phas, sakshamguruji, saneryee
630.4686 USDC - $630.47
Issue | Instances | Total Gas Saved | |
---|---|---|---|
[Gā01] | State variables only set in the constructor should be declared immutable | 2 | 2097 |
[Gā02] | Structs can be packed into fewer storage slots | 2 | - |
[Gā03] | Using storage instead of memory for structs/arrays saves gas | 1 | 4200 |
[Gā04] | Avoid contract existence checks by using low level calls | 11 | 1100 |
[Gā05] | The result of function calls should be cached rather than re-calling the function | 4 | - |
[Gā06] | <x> += <y> costs more gas than <x> = <x> + <y> for state variables | 4 | 452 |
[Gā07] | internal functions only called once can be inlined to save gas | 11 | 220 |
[Gā08] | Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if -statement | 1 | 85 |
[Gā09] | ++i /i++ should be unchecked{++i} /unchecked{i++} when it is not possible for them to overflow, as is the case when used in for - and while -loops | 14 | 840 |
[Gā10] | require() /revert() strings longer than 32 bytes cost extra gas | 4 | - |
[Gā11] | Optimize names to save gas | 17 | 374 |
[Gā12] | Use a more recent version of solidity | 18 | - |
[Gā13] | >= costs less gas than > | 2 | 6 |
[Gā14] | ++i costs less gas than i++ , especially when it's used in for -loops (--i /i-- too) | 1 | 5 |
[Gā15] | Splitting require() statements that use && saves gas | 12 | 36 |
[Gā16] | Usage of uints /ints smaller than 32 bytes (256 bits) incurs overhead | 50 | - |
[Gā17] | Inverting the condition of an if -else -statement wastes gas | 1 | - |
[Gā18] | require() or revert() statements that check input arguments should be at the top of the function | 2 | - |
[Gā19] | Functions guaranteed to revert when called by normal users can be marked payable | 2 | 42 |
Total: 159 instances over 19 issues with 9460 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. The table above as well as its gas numbers do not include any of the excluded findings.
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).
In the example below, twa.lookback
never changes, so it should be pulled out of the struct and converted to an immutable value. If you still want the struct to contain the lookback, create a private version of the twa struct, for storage purposes, that does not have the lookback
, and use that to build the original twa structs on the fly, when requested, and pass the value as an argument to TWA.getTwa()
when it's called. Since this is in the hot path, you'll save a lot of gas.
There are 2 instances of this issue:
File: maverick-v1/contracts/models/Pool.sol /// @audit twa.lookback (constructor) 77: twa.lookback = _lookback; /// @audit twa (access) 102: return twa;
Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings
There are 2 instances of this issue:
File: maverick-v1/contracts/libraries/Delta.sol /// @audit Variable ordering with 10 slots instead of the current 11: /// uint256(32):deltaInBinInternal, uint256(32):deltaInErc, uint256(32):deltaOutErc, uint256(32):excess, uint256(32):endSqrtPrice, uint256(32):sqrtPriceLimit, uint256(32):sqrtLowerTickPrice, uint256(32):sqrtUpperTickPrice, uint256(32):sqrtPrice, bool(1):tokenAIn, bool(1):exactOutput, bool(1):swappedToMaxPrice, bool(1):skipCombine, bool(1):decrementTick 5 struct Instance { 6 uint256 deltaInBinInternal; 7 uint256 deltaInErc; 8 uint256 deltaOutErc; 9 uint256 excess; 10 bool tokenAIn; 11 uint256 endSqrtPrice; 12 bool exactOutput; 13 bool swappedToMaxPrice; 14 bool skipCombine; 15 bool decrementTick; 16 uint256 sqrtPriceLimit; 17 uint256 sqrtLowerTickPrice; 18 uint256 sqrtUpperTickPrice; 19 uint256 sqrtPrice; 20: }
File: maverick-v1/contracts/models/Pool.sol /// @audit Variable ordering with 8 slots instead of the current 10: /// uint8[2](32):kinds, user-defined[](32):activeList, uint256(32):mergeBinBalance, uint256(32):binCounter, uint256(32):mergeBinCounter, uint128[](32):mergeBins, uint128(16):firstBinId, uint128(16):totalReserveA, uint128(16):totalReserveB, int32(4):tickLimit, int32(4):shiftAmount 363 struct MoveData { 364 int32 tickLimit; 365 uint8[2] kinds; 366 int32 shiftAmount; 367 BinMap.Active[] activeList; 368 uint128 firstBinId; 369 uint256 mergeBinBalance; 370 uint128 totalReserveA; 371 uint128 totalReserveB; 372 uint256 binCounter; 373 uint256 mergeBinCounter; 374 uint128[] mergeBins; 375: }
storage
instead of memory
for structs/arrays saves gasWhen 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: maverick-v1/contracts/models/PoolInspector.sol 102: IPool.BinState memory bin = bins[i];
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 11 instances of this issue:
File: maverick-v1/contracts/models/Factory.sol /// @audit decimals() 74: Math.scale(IERC20Metadata(address(_tokenA)).decimals()), /// @audit decimals() 75: Math.scale(IERC20Metadata(address(_tokenB)).decimals()),
File: maverick-v1/contracts/models/Pool.sol /// @audit balanceOf() 502: return IERC20(tokenA).balanceOf(address(this)); /// @audit balanceOf() 506: return IERC20(tokenB).balanceOf(address(this));
File: router-v1/contracts/libraries/SelfPermit.sol /// @audit allowance() 22: if (IERC20(token).allowance(msg.sender, address(this)) < value) selfPermit(token, value, deadline, v, r, s); /// @audit allowance() 32: if (IERC20(token).allowance(msg.sender, address(this)) < type(uint256).max) selfPermitAllowed(token, nonce, expiry, v, r, s);
File: router-v1/contracts/Router.sol /// @audit tokenOfOwnerByIndexExists() 223: if (IPosition(position).tokenOfOwnerByIndexExists(msg.sender, 0)) { /// @audit tokenOfOwnerByIndex() 224: tokenId = IPosition(position).tokenOfOwnerByIndex(msg.sender, 0); /// @audit mint() 226: tokenId = IPosition(position).mint(msg.sender); /// @audit lookup() 269: pool = IFactory(factory).lookup(poolParams.fee, poolParams.tickSpacing, poolParams.lookback, poolParams.tokenA, poolParams.tokenB); /// @audit create() 272: pool = IFactory(factory).create(poolParams.fee, poolParams.tickSpacing, poolParams.lookback, poolParams.activeTick, poolParams.tokenA, poolParams.tokenB);
The instances below point to the second+ call of the function within a single function
There are 4 instances of this issue:
File: maverick-v1/contracts/models/Pool.sol /// @audit sqrtPrice.inv() on line 589 589: Math.mulDiv(binAmountIn, tokenAIn ? sqrtPrice.inv() : sqrtPrice, binAmountIn.div(liquidity) + (tokenAIn ? sqrtPrice : sqrtPrice.inv()), false) /// @audit sqrtPrice.inv() on line 589 591: delta.endSqrtPrice = binAmountIn.div(liquidity) + (tokenAIn ? sqrtPrice : sqrtPrice.inv()); /// @audit sqrtPrice.inv() on line 602 602: binAmountIn = Math.mulDiv(delta.deltaOutErc, tokenAIn ? sqrtPrice : sqrtPrice.inv(), (tokenAIn ? sqrtPrice.inv() : sqrtPrice) - delta.deltaOutErc.div(liquidity), true); /// @audit sqrtPrice.inv() on line 602 603: delta.endSqrtPrice = (tokenAIn ? sqrtPrice.inv() : sqrtPrice) - delta.deltaOutErc.div(liquidity);
<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesUsing the addition operator instead of plus-equals saves 113 gas
There are 4 instances of this issue:
File: maverick-v1/contracts/models/Pool.sol 161: binBalanceA += tokenAAmount.toUint128(); 162: binBalanceB += tokenBAmount.toUint128(); 288: binBalanceA += delta.deltaInBinInternal.toUint128(); 291: binBalanceB += delta.deltaInBinInternal.toUint128();
internal
functions only called once can be inlined to save gasNot inlining costs 20 to 40 gas because of two extra JUMP
instructions and additional stack operations needed for function calls.
There are 11 instances of this issue:
File: maverick-v1/contracts/models/PoolInspector.sol 77: function _getBinsAtTick(IPool pool, int32 tick) internal view returns (IPool.BinState[] memory bins) { 95: function _currentTickLiquidity(IPool pool) internal view returns (uint256 sqrtPrice, uint256 liquidity, uint256 reserveA, uint256 reserveB) {
File: maverick-v1/contracts/models/Pool.sol 320: function claimProtocolFees(address recipient, bool isTokenA) internal { 332: function setProtocolFeeRatio(uint16 _protocolFeeRatio) internal { 448: function _moveBins(int32 activeTick, int32 startingTickInitial, int32 lastTwap) internal { 486: function _getOrCreateBin(State memory currentState, uint8 kind, int32 pos, bool isDelta) internal returns (uint128 binId, Bin.Instance storage bin) { 516: function _currentTickLiquidity(int32 tick) internal view returns (uint256 sqrtLowerTickPrice, uint256 sqrtUpperTickPrice, uint256 sqrtPrice, uint128[] memory binIds, SwapData memory output) { 537: function _firstKindAtTickLiquidity(int32 activeTick) internal view returns (uint256 currentReserveA, uint256 currentReserveB) { 553 function _computeSwapExactIn( 554 uint256 sqrtEdgePrice, 555 uint256 sqrtPrice, 556 uint256 liquidity, 557 uint256 reserveA, 558 uint256 reserveB, 559 uint256 amountIn, 560 bool limitInBin, 561 bool tokenAIn 562: ) internal view returns (Delta.Instance memory delta) { 597: function _computeSwapExactOut(uint256 sqrtPrice, uint256 liquidity, uint256 reserveA, uint256 reserveB, uint256 amountOut, bool tokenAIn) internal view returns (Delta.Instance memory delta) { 614: function _swapTick(State memory currentState, Delta.Instance memory delta) internal returns (Delta.Instance memory newDelta) {
unchecked {}
for subtractions where the operands cannot underflow because of a previous require()
or if
-statementrequire(a <= b); x = b - a
=> require(a <= b); unchecked { x = b - a }
There is 1 instance of this issue:
File: maverick-v1/contracts/libraries/Math.sol /// @audit if-condition on line 41 42: return (10 ** (decimals - DEFAULT_DECIMALS)) | MAX_BIT;
++i
/i++
should be unchecked{++i}
/unchecked{i++}
when it is not possible for them to overflow, as is the case when used in for
- and while
-loopsThe unchecked
keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop
There are 14 instances of this issue:
File: maverick-v1/contracts/models/PoolInspector.sol 30: for (uint128 i = startBinIndex; i < binCounter; i++) { 80: for (uint8 i = 0; i < NUMBER_OF_KINDS; i++) { 101: for (uint256 i; i < bins.length; i++) {
File: maverick-v1/contracts/models/Pool.sol 127: for (uint256 i; i < params.length; i++) { 180: for (uint256 i; i < binIds.length; i++) { 193: for (uint256 i; i < params.length; i++) { 224: for (uint256 i; i < params.length; i++) { 380: for (uint256 j; j < 2; j++) { 389: for (uint256 i; i <= moveData.binCounter; i++) { 414: for (uint256 i; i < moveData.mergeBinCounter; i++) { 523: for (uint256 i; i < NUMBER_OF_KINDS; i++) { 540: for (uint256 i; i < NUMBER_OF_KINDS; i++) { 653: for (uint256 i; i < swapData.counter; i++) {
File: router-v1/contracts/libraries/Multicall.sol 13: for (uint256 i = 0; i < data.length; i++) {
require()
/revert()
strings longer than 32 bytes cost extra gasEach extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas
There are 4 instances of this issue:
File: maverick-v1/contracts/models/Factory.sol 27: require(_protocolFeeRatio <= ONE_3_DECIMAL_SCALE, "Factory:PROTOCOL_FEE_CANNOT_EXCEED_ONE"); 35: require(_protocolFeeRatio <= ONE_3_DECIMAL_SCALE, "Factory:PROTOCOL_FEE_CANNOT_EXCEED_ONE"); 59: require(_tokenA < _tokenB, "Factory:TOKENS_MUST_BE_SORTED_BY_ADDRESS"); 61: require(_tickSpacing > 0, "Factory:TICK_SPACING_OUT_OF_BOUNDS");
public
/external
function names and public
member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted
There are 17 instances of this issue:
File: maverick-v1/contracts/interfaces/IAddLiquidityCallback.sol /// @audit addLiquidityCallback() 4: interface IAddLiquidityCallback {
File: maverick-v1/contracts/interfaces/IFactory.sol /// @audit create(), lookup(), position(), protocolFeeRatio(), isFactoryPool() 8: interface IFactory {
File: maverick-v1/contracts/interfaces/IPoolAdmin.sol /// @audit adminAction() 4: interface IPoolAdmin {
File: maverick-v1/contracts/interfaces/IPool.sol /// @audit fee(), tickSpacing(), tokenA(), tokenB(), factory(), binMap(), binPositions(), binBalanceA(), binBalanceB(), getTwa(), getState(), addLiquidity(), transferLiquidity(), removeLiquidity(), migrateBinsUpStack(), swap(), getBin(), balanceOf() 7: interface IPool {
File: maverick-v1/contracts/interfaces/IPosition.sol /// @audit mint(), tokenOfOwnerByIndexExists() 7: interface IPosition is IERC721Enumerable {
File: maverick-v1/contracts/interfaces/ISwapCallback.sol /// @audit swapCallback() 4: interface ISwapCallback {
File: maverick-v1/contracts/libraries/Deployer.sol /// @audit deploy() 11: library Deployer {
File: maverick-v1/contracts/models/Factory.sol /// @audit setProtocolFeeRatio(), setOwner() 14: contract Factory is IFactory {
File: maverick-v1/contracts/models/PoolInspector.sol /// @audit getActiveBins(), getBinDepth(), calculateSwap(), getPrice() 11: contract PoolInspector is ISwapCallback {
File: maverick-v1/contracts/models/Pool.sol /// @audit swap(), adminAction() 22: contract Pool is IPool, IPoolAdmin {
File: maverick-v1/contracts/models/PositionMetadata.sol /// @audit setBaseURI() 8: contract PositionMetadata is IPositionMetadata, Ownable {
File: maverick-v1/contracts/models/Position.sol /// @audit setMetadata(), mint(), tokenOfOwnerByIndexExists() 12: contract Position is ERC721, ERC721Enumerable, Ownable, IPosition {
File: router-v1/contracts/interfaces/external/IERC20PermitAllowed.sol /// @audit permit() 6: interface IERC20PermitAllowed {
File: router-v1/contracts/interfaces/IMulticall.sol /// @audit multicall() 7: interface IMulticall {
File: router-v1/contracts/interfaces/IRouter.sol /// @audit factory(), position(), WETH9(), exactInputSingle(), exactInput(), exactOutputSingle(), exactOutput(), unwrapWETH9(), refundETH(), sweepToken(), getOrCreatePoolAndAddLiquidity(), addLiquidityToPool(), addLiquidityWTickLimits(), migrateBinsUpStack(), removeLiquidity() 11: interface IRouter is ISwapCallback {
File: router-v1/contracts/interfaces/ISelfPermit.sol /// @audit selfPermit(), selfPermitIfNecessary(), selfPermitAllowed(), selfPermitAllowedIfNecessary() 6: interface ISelfPermit {
File: router-v1/contracts/Router.sol /// @audit sweepToken(), swapCallback(), addLiquidityCallback(), addLiquidityToPool(), addLiquidityWTickLimits(), getOrCreatePoolAndAddLiquidity(), migrateBinsUpStack(), removeLiquidity() 18: contract Router is IRouter, Multicall, SelfPermit, Deadline {
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 18 instances of this issue:
File: maverick-v1/contracts/libraries/BinMap.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/BinMath.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/Bin.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/Cast.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/Constants.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/Delta.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/Deployer.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/Math.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/SafeERC20Min.sol 3: pragma solidity ^0.8.0;
File: maverick-v1/contracts/libraries/Twa.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/models/Factory.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/models/PoolInspector.sol 2: pragma solidity ^0.8.0;
File: maverick-v1/contracts/models/Pool.sol 2: pragma solidity ^0.8.0;
File: router-v1/contracts/interfaces/external/IWETH9.sol 2: pragma solidity ^0.8.0;
File: router-v1/contracts/libraries/Deadline.sol 2: pragma solidity ^0.8.0;
File: router-v1/contracts/libraries/Multicall.sol 2: pragma solidity ^0.8.0;
File: router-v1/contracts/libraries/Path.sol 2: pragma solidity ^0.8.0;
File: router-v1/contracts/Router.sol 2: pragma solidity ^0.8.0;
>=
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: maverick-v1/contracts/libraries/Math.sol 9: return x > y ? x : y; 17: return x > y ? x : y;
++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: maverick-v1/contracts/models/PoolInspector.sol 85: binCounter--;
require()
statements that use &&
saves gasSee 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 12 instances of this issue:
File: maverick-v1/contracts/models/Factory.sol 41: require(msg.sender == owner && _owner != address(0), "Factory:NOT_ALLOWED"); 60: require(_fee > 0 && _fee < 1e18, "Factory:FEE_OUT_OF_BOUNDS"); 62: require(_lookback >= 3600 && _lookback <= uint16(type(int16).max), "Factory:LOOKBACK_OUT_OF_BOUNDS");
File: maverick-v1/contracts/models/Pool.sol 93: require((currentState.status & LOCKED == 0) && (allowInEmergency || (currentState.status & EMERGENCY == 0)), "E"); 168: require(previousABalance + tokenAAmount <= _tokenABalance() && previousBBalance + tokenBAmount <= _tokenBBalance(), "A");
File: router-v1/contracts/libraries/TransferHelper.sol 15: require(success && (data.length == 0 || abi.decode(data, (bool))), "STF"); 25: require(success && (data.length == 0 || abi.decode(data, (bool))), "ST"); 35: require(success && (data.length == 0 || abi.decode(data, (bool))), "SA");
File: router-v1/contracts/Router.sol 100: require(amountToPay > 0 && amountOut > 0, "In or Out Amount is Zero"); 234: require(tokenAAmount >= minTokenAAmount && tokenBAmount >= minTokenBAmount, "Too little added"); 262: require(activeTick >= minActiveTick && activeTick <= maxActiveTick, "activeTick not in range"); 309: require(tokenAAmount >= minTokenAAmount && tokenBAmount >= minTokenBAmount, "Too little removed");
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen 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.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html
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
There are 50 instances of this issue:
File: maverick-v1/contracts/libraries/BinMap.sol /// @audit uint8 offset 21: offset = uint8(uint32(tick_kinds)); /// @audit int32 mapIndex 22: mapIndex = tick_kinds >> 8; /// @audit uint16 offset 54: offset += bitIndex; /// @audit uint16 offset 68: offset += uint16(uint32(NUMBER_OF_KINDS_32)); /// @audit int32 wordIndex 70: wordIndex += 1; /// @audit uint16 offset 71: offset = 0; /// @audit int32 tack 86: tack = 1; /// @audit uint16 shift 88: shift = uint16(WORD_SIZE - offset); /// @audit int32 tack 89: tack = -1; /// @audit uint16 shift 96: shift = 0; /// @audit uint16 subIndex 100: subIndex = isRight ? BinMath.lsb(nextWord) + shift : BinMath.msb(nextWord) - shift; /// @audit int32 nextTick 103: nextTick = posFirst >> 2;
File: maverick-v1/contracts/libraries/BinMath.sol /// @audit uint8 result 14: result = 0; /// @audit uint8 result 18: result += 128; /// @audit uint8 result 22: result += 64; /// @audit uint8 result 26: result += 32; /// @audit uint8 result 30: result += 16; /// @audit uint8 result 34: result += 8; /// @audit uint8 result 38: result += 4; /// @audit uint8 result 42: result += 2; /// @audit uint8 result 44: if (x >= 0x2) result += 1; /// @audit uint8 result 50: result = 255; /// @audit uint8 result 53: result -= 128; /// @audit uint8 result 58: result -= 64; /// @audit uint8 result 63: result -= 32; /// @audit uint8 result 68: result -= 16; /// @audit uint8 result 73: result -= 8; /// @audit uint8 result 78: result -= 4; /// @audit uint8 result 83: result -= 2; /// @audit uint8 result 87: if (x & 0x1 > 0) result -= 1;
File: maverick-v1/contracts/libraries/Bin.sol /// @audit uint128 deltaIn 61: deltaIn = Math.mulDiv(delta.deltaInBinInternal, thisBinAmount, totalAmount, false).toUint128(); /// @audit uint128 deltaOut 63: deltaOut = Math.mulDiv(delta.deltaOutErc, thisBinAmount, totalAmount, true).toUint128(); /// @audit uint32 maxRecursion 97: maxRecursion = maxRecursion == 0 ? type(uint32).max : maxRecursion; /// @audit uint32 maxRecursion 114: maxRecursion -= 1; /// @audit uint128 delta 122: delta = Math.min(Math.mulDiv(tokenAmount, reserve, totalSupply, false), reserve).toUint128(); /// @audit uint128 reserveOut 123: reserveOut = delta == 0 ? reserve : Math.clip128(reserve, delta + 1); /// @audit uint128 deltaLpBalance128 146: deltaLpBalance128 = Math.min(self.state.mergeBinBalance, Math.mulDiv(deltaLpBalance, self.state.mergeBinBalance, tempTotalSupply, false)).toUint128();
File: maverick-v1/contracts/libraries/Cast.sol /// @audit uint128 y 6: require((y = uint128(x)) == x, "C");
File: maverick-v1/contracts/models/PoolInspector.sol /// @audit uint128 binCounter 26: binCounter = binCounter < endBinIndex ? binCounter : endBinIndex; /// @audit uint128 binId 50: binId = bin.mergeId;
File: maverick-v1/contracts/models/Pool.sol /// @audit uint128 binBalanceA 161: binBalanceA += tokenAAmount.toUint128(); /// @audit uint128 binBalanceB 162: binBalanceB += tokenBAmount.toUint128(); /// @audit uint128 binBalanceA 242: binBalanceA = Math.clip128(binBalanceA, tokenAOut.toUint128()); /// @audit uint128 binBalanceB 243: binBalanceB = Math.clip128(binBalanceB, tokenBOut.toUint128()); /// @audit uint128 binBalanceA 288: binBalanceA += delta.deltaInBinInternal.toUint128(); /// @audit uint128 binBalanceB 289: binBalanceB = Math.clip128(binBalanceB, delta.deltaOutErc.toUint128()); /// @audit uint128 binBalanceB 291: binBalanceB += delta.deltaInBinInternal.toUint128(); /// @audit uint128 binBalanceA 292: binBalanceA = Math.clip128(binBalanceA, delta.deltaOutErc.toUint128()); /// @audit uint128 binId 492: binId = currentState.binCounter; /// @audit int32 activeTick 622: activeTick = binMap.nextActive(activeTick, delta.tokenAIn);
if
-else
-statement wastes gasFlipping the true
and false
blocks instead saves 3 gas
There is 1 instance of this issue:
File: maverick-v1/contracts/models/Pool.sol 270: delta.excess = (!exactOutput) ? Math.fromScale(amount, tokenAIn ? tokenAScale : tokenBScale) : Math.fromScale(amount, !tokenAIn ? tokenAScale : tokenBScale);
require()
or revert()
statements that check input arguments should be at the top of the functionChecks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas*) in a function that may ultimately revert in the unhappy case.
There are 2 instances of this issue:
File: maverick-v1/contracts/models/Factory.sol /// @audit expensive op on line 34 35: require(_protocolFeeRatio <= ONE_3_DECIMAL_SCALE, "Factory:PROTOCOL_FEE_CANNOT_EXCEED_ONE"); /// @audit expensive op on line 60 61: require(_tickSpacing > 0, "Factory:TICK_SPACING_OUT_OF_BOUNDS");
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 2 instances of this issue:
File: maverick-v1/contracts/models/PositionMetadata.sol 17: function setBaseURI(string memory _baseURI) external onlyOwner {
File: maverick-v1/contracts/models/Position.sol 23: function setMetadata(IPositionMetadata _metadata) external onlyOwner {
These findings are excluded from awards calculations because there are publicly-available automated tools that find them. The valid ones appear here for completeness
Issue | Instances | Total Gas Saved | |
---|---|---|---|
[Gā01] | Using calldata instead of memory for read-only arguments in external functions saves gas | 2 | 240 |
[Gā02] | State variables should be cached in stack variables rather than re-reading them from storage | 2 | 194 |
[Gā03] | <array>.length should not be looked up in every loop of a for -loop | 6 | 18 |
[Gā04] | Using bool s for storage incurs overhead | 1 | 17100 |
[Gā05] | Using > 0 costs more gas than != 0 when used on a uint in a require() statement | 1 | 6 |
[Gā06] | ++i costs less gas than i++ , especially when it's used in for -loops (--i /i-- too) | 19 | 95 |
[Gā07] | Use custom errors rather than revert() /require() strings to save gas | 38 | - |
Total: 69 instances over 7 issues with 17653 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. The table above as well as its gas numbers do not include any of the excluded findings.
calldata
instead of memory
for read-only arguments in external
functions saves gasWhen 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 are 2 instances of this issue:
File: maverick-v1/contracts/models/PositionMetadata.sol /// @audit _baseURI - (valid but excluded finding) 17: function setBaseURI(string memory _baseURI) external onlyOwner {
File: router-v1/contracts/Router.sol /// @audit params - (valid but excluded finding) 143: function exactInput(ExactInputParams memory params) external payable override checkDeadline(params.deadline) returns (uint256 amountOut) {
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 2 instances of this issue:
File: maverick-v1/contracts/models/Factory.sol /// @audit protocolFeeRatio on line 71 - (valid but excluded finding) 80: emit PoolCreated(address(pool), _fee, _tickSpacing, _activeTick, _lookback, protocolFeeRatio, _tokenA, _tokenB);
File: maverick-v1/contracts/models/PositionMetadata.sol /// @audit baseURI on line 22 - (valid but excluded finding) 22: return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
<array>.length
should not be looked up in every loop of a for
-loopThe overheads outlined below are PER LOOP, excluding the first loop
MLOAD
(3 gas)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 are 6 instances of this issue:
File: maverick-v1/contracts/models/PoolInspector.sol /// @audit (valid but excluded finding) 101: for (uint256 i; i < bins.length; i++) {
File: maverick-v1/contracts/models/Pool.sol /// @audit (valid but excluded finding) 127: for (uint256 i; i < params.length; i++) { /// @audit (valid but excluded finding) 180: for (uint256 i; i < binIds.length; i++) { /// @audit (valid but excluded finding) 193: for (uint256 i; i < params.length; i++) { /// @audit (valid but excluded finding) 224: for (uint256 i; i < params.length; i++) {
File: router-v1/contracts/libraries/Multicall.sol /// @audit (valid but excluded finding) 13: for (uint256 i = 0; i < data.length; i++) {
bool
s for storage incurs overhead// Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27
Use uint256(1)
and uint256(2)
for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from false
to true
, after having been true
in the past
There is 1 instance of this issue:
File: maverick-v1/contracts/models/Factory.sol /// @audit (valid but excluded finding) 18: mapping(IPool => bool) public override isFactoryPool;
> 0
costs more gas than != 0
when used on a uint
in a require()
statementThis change saves 6 gas per instance. The optimization works until solidity version 0.8.13 where there is a regression in gas costs.
There is 1 instance of this issue:
File: maverick-v1/contracts/models/Factory.sol /// @audit (valid but excluded finding) 61: require(_tickSpacing > 0, "Factory:TICK_SPACING_OUT_OF_BOUNDS");
++i
costs less gas than i++
, especially when it's used in for
-loops (--i
/i--
too)Saves 5 gas per loop
There are 19 instances of this issue:
File: maverick-v1/contracts/models/PoolInspector.sol /// @audit (valid but excluded finding) 30: for (uint128 i = startBinIndex; i < binCounter; i++) { /// @audit (valid but excluded finding) 34: activeCounter++; /// @audit (valid but excluded finding) 49: depth++; /// @audit (valid but excluded finding) 80: for (uint8 i = 0; i < NUMBER_OF_KINDS; i++) { /// @audit (valid but excluded finding) 101: for (uint256 i; i < bins.length; i++) {
File: maverick-v1/contracts/models/Pool.sol /// @audit (valid but excluded finding) 127: for (uint256 i; i < params.length; i++) { /// @audit (valid but excluded finding) 180: for (uint256 i; i < binIds.length; i++) { /// @audit (valid but excluded finding) 193: for (uint256 i; i < params.length; i++) { /// @audit (valid but excluded finding) 224: for (uint256 i; i < params.length; i++) { /// @audit (valid but excluded finding) 380: for (uint256 j; j < 2; j++) { /// @audit (valid but excluded finding) 389: for (uint256 i; i <= moveData.binCounter; i++) { /// @audit (valid but excluded finding) 396: moveData.mergeBinCounter++; /// @audit (valid but excluded finding) 409: moveData.mergeBinCounter++; /// @audit (valid but excluded finding) 414: for (uint256 i; i < moveData.mergeBinCounter; i++) { /// @audit (valid but excluded finding) 523: for (uint256 i; i < NUMBER_OF_KINDS; i++) { /// @audit (valid but excluded finding) 530: output.counter++; /// @audit (valid but excluded finding) 540: for (uint256 i; i < NUMBER_OF_KINDS; i++) { /// @audit (valid but excluded finding) 653: for (uint256 i; i < swapData.counter; i++) {
File: router-v1/contracts/libraries/Multicall.sol /// @audit (valid but excluded finding) 13: for (uint256 i = 0; i < data.length; i++) {
revert()
/require()
strings to save gasCustom 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 38 instances of this issue:
File: maverick-v1/contracts/libraries/BinMath.sol /// @audit (valid but excluded finding) 94: require(tick <= MAX_TICK, "X");
File: maverick-v1/contracts/libraries/Bin.sol /// @audit (valid but excluded finding) 85: require(deltaLpToken != 0, "L"); /// @audit (valid but excluded finding) 140: require(activeBin.state.mergeId == 0, "N");
File: maverick-v1/contracts/libraries/Cast.sol /// @audit (valid but excluded finding) 6: require((y = uint128(x)) == x, "C");
File: maverick-v1/contracts/libraries/SafeERC20Min.sol /// @audit (valid but excluded finding) 18: require(abi.decode(returndata, (bool)), "T");
File: maverick-v1/contracts/models/Factory.sol /// @audit (valid but excluded finding) 27: require(_protocolFeeRatio <= ONE_3_DECIMAL_SCALE, "Factory:PROTOCOL_FEE_CANNOT_EXCEED_ONE"); /// @audit (valid but excluded finding) 34: require(msg.sender == owner, "Factory:NOT_ALLOWED"); /// @audit (valid but excluded finding) 35: require(_protocolFeeRatio <= ONE_3_DECIMAL_SCALE, "Factory:PROTOCOL_FEE_CANNOT_EXCEED_ONE"); /// @audit (valid but excluded finding) 41: require(msg.sender == owner && _owner != address(0), "Factory:NOT_ALLOWED"); /// @audit (valid but excluded finding) 59: require(_tokenA < _tokenB, "Factory:TOKENS_MUST_BE_SORTED_BY_ADDRESS"); /// @audit (valid but excluded finding) 60: require(_fee > 0 && _fee < 1e18, "Factory:FEE_OUT_OF_BOUNDS"); /// @audit (valid but excluded finding) 61: require(_tickSpacing > 0, "Factory:TICK_SPACING_OUT_OF_BOUNDS"); /// @audit (valid but excluded finding) 62: require(_lookback >= 3600 && _lookback <= uint16(type(int16).max), "Factory:LOOKBACK_OUT_OF_BOUNDS"); /// @audit (valid but excluded finding) 64: require(pools[_fee][_tickSpacing][_lookback][_tokenA][_tokenB] == IPool(address(0)), "Factory:POOL_ALREADY_EXISTS");
File: maverick-v1/contracts/models/Pool.sol /// @audit (valid but excluded finding) 87: require(msg.sender == position.ownerOf(tokenId) || msg.sender == position.getApproved(tokenId), "P"); /// @audit (valid but excluded finding) 93: require((currentState.status & LOCKED == 0) && (allowInEmergency || (currentState.status & EMERGENCY == 0)), "E"); /// @audit (valid but excluded finding) 168: require(previousABalance + tokenAAmount <= _tokenABalance() && previousBBalance + tokenBAmount <= _tokenBBalance(), "A"); /// @audit (valid but excluded finding) 303: require(previousBalance + amountIn <= (tokenAIn ? _tokenABalance() : _tokenBBalance()), "S");
File: maverick-v1/contracts/models/Position.sol /// @audit (valid but excluded finding) 43: require(_exists(tokenId), "Invalid Token ID");
File: router-v1/contracts/libraries/Deadline.sol /// @audit (valid but excluded finding) 6: require(block.timestamp <= deadline, "Transaction too old");
File: router-v1/contracts/libraries/TransferHelper.sol /// @audit (valid but excluded finding) 15: require(success && (data.length == 0 || abi.decode(data, (bool))), "STF"); /// @audit (valid but excluded finding) 25: require(success && (data.length == 0 || abi.decode(data, (bool))), "ST"); /// @audit (valid but excluded finding) 35: require(success && (data.length == 0 || abi.decode(data, (bool))), "SA"); /// @audit (valid but excluded finding) 44: require(success, "STE");
File: router-v1/contracts/Router.sol /// @audit (valid but excluded finding) 55: require(IWETH9(msg.sender) == WETH9, "Not WETH9"); /// @audit (valid but excluded finding) 61: require(balanceWETH9 >= amountMinimum, "Insufficient WETH9"); /// @audit (valid but excluded finding) 72: require(balanceToken >= amountMinimum, "Insufficient token"); /// @audit (valid but excluded finding) 100: require(amountToPay > 0 && amountOut > 0, "In or Out Amount is Zero"); /// @audit (valid but excluded finding) 101: require(factory.isFactoryPool(IPool(msg.sender)), "Must call from a Factory Pool"); /// @audit (valid but excluded finding) 139: require(amountOut >= params.amountOutMinimum, "Too little received"); /// @audit (valid but excluded finding) 165: require(amountOut >= params.amountOutMinimum, "Too little received"); /// @audit (valid but excluded finding) 177: require(amountOutReceived == amountOut, "Requested amount not available"); /// @audit (valid but excluded finding) 188: require(amountIn <= params.amountInMaximum, "Too much requested"); /// @audit (valid but excluded finding) 197: require(amountIn <= params.amountInMaximum, "Too much requested"); /// @audit (valid but excluded finding) 234: require(tokenAAmount >= minTokenAAmount && tokenBAmount >= minTokenBAmount, "Too little added"); /// @audit (valid but excluded finding) 262: require(activeTick >= minActiveTick && activeTick <= maxActiveTick, "activeTick not in range"); /// @audit (valid but excluded finding) 304: require(msg.sender == position.ownerOf(tokenId), "P"); /// @audit (valid but excluded finding) 309: require(tokenAAmount >= minTokenAAmount && tokenBAmount >= minTokenBAmount, "Too little removed");
#0 - c4-judge
2023-01-18T21:52:57Z
kirk-baird marked the issue as grade-a
#1 - c4-judge
2023-01-23T23:29:11Z
kirk-baird marked the issue as selected for report