code-423n4 / 2022-02-aave-lens-findings

0 stars 0 forks source link

Gas Optimizations #2

Closed code423n4 closed 2 years ago

code423n4 commented 2 years ago

Title: State variables that could be set immutable Severity: GAS

In the following files there are state variables that could be set immutable to save gas.

    HUB in CollectNFT.sol
    MODULE_GLOBALS in FeeModuleBase.sol
    COLLECT_NFT_IMPL in LensHub.sol
    HUB in ModuleBase.sol
    FOLLOW_NFT_IMPL in LensHub.sol
    LENS_HUB in MockProfileCreationProxy.sol
    originalImpl in VersionedInitializable.sol
    HUB in FollowNFT.sol

Title: Unused state variables Severity: GAS

Unused state variables are gas consuming at deployment (since they are located in storage) and are a bad code practice. Removing those variables will decrease deployment gas cost and improve code quality. This is a full list of all the unused storage variables we found in your code base.

    FeeModuleBase.sol, BPS_MAX
    LensHubStorage.sol, _referenceModuleWhitelisted
    Constants.sol, FOLLOW_NFT_NAME_SUFFIX
    Constants.sol, MAX_HANDLE_LENGTH
    MockLensHubV2Storage.sol, SET_DISPATCHER_WITH_SIG_TYPEHASH
    MockLensHubV2Storage.sol, FOLLOW_WITH_SIG_TYPEHASH
    LensHubStorage.sol, COLLECT_WITH_SIG_TYPEHASH
    MockLensHubV2Storage.sol, SET_FOLLOW_MODULE_WITH_SIG_TYPEHASH
    MockLensHubV2Storage.sol, _referenceModuleWhitelisted
    LensHubStorage.sol, SET_DISPATCHER_WITH_SIG_TYPEHASH
    LensHubStorage.sol, _profileCreatorWhitelisted
    MockLensHubV2Storage.sol, CREATE_PROFILE_WITH_SIG_TYPEHASH
    MockLensHubV2Storage.sol, COMMENT_WITH_SIG_TYPEHASH
    Constants.sol, COLLECT_NFT_NAME_INFIX
    LensHubStorage.sol, SET_FOLLOW_NFT_URI_WITH_SIG_TYPEHASH
    LensHubStorage.sol, CREATE_PROFILE_WITH_SIG_TYPEHASH
    LensHubStorage.sol, MIRROR_WITH_SIG_TYPEHASH
    LensHubStorage.sol, _governance
    LensHubStorage.sol, SET_FOLLOW_MODULE_WITH_SIG_TYPEHASH
    MockLensHubV2Storage.sol, _emergencyAdmin
    MockLensHubV2Storage.sol, COLLECT_WITH_SIG_TYPEHASH
    LensHubStorage.sol, _dispatcherByProfile
    MockLensHubV2Storage.sol, SET_PROFILE_IMAGE_URI_WITH_SIG_TYPEHASH
    MockLensHubV2Storage.sol, _additionalValue
    LensHubStorage.sol, _collectModuleWhitelisted
    MockLensHubV2Storage.sol, _profileCounter
    LensHubStorage.sol, _profileIdByHandleHash
    MockLensHubV2Storage.sol, _profileIdByHandleHash
    LensHubStorage.sol, _followModuleWhitelisted
    MockLensHubV2Storage.sol, _collectModuleWhitelisted
    LensHubStorage.sol, FOLLOW_WITH_SIG_TYPEHASH
    Constants.sol, COLLECT_NFT_SYMBOL_INFIX
    MockLensHubV2Storage.sol, _profileById
    LensHubStorage.sol, SET_PROFILE_IMAGE_URI_WITH_SIG_TYPEHASH
    MockLensHubV2Storage.sol, POST_WITH_SIG_TYPEHASH
    MockLensHubV2Storage.sol, _followModuleWhitelisted
    MockLensHubV2Storage.sol, _dispatcherByProfile
    Constants.sol, FOLLOW_NFT_SYMBOL_SUFFIX
    MockLensHubV2Storage.sol, MIRROR_WITH_SIG_TYPEHASH
    LensHubStorage.sol, _emergencyAdmin
    LensHubStorage.sol, POST_WITH_SIG_TYPEHASH
    LensHubStorage.sol, _profileById
    LensHubStorage.sol, _profileCounter
    MockLensHubV2Storage.sol, _pubByIdByProfile
    LensHubStorage.sol, COMMENT_WITH_SIG_TYPEHASH
    MockLensHubV2Storage.sol, _governance
    LensHubStorage.sol, _pubByIdByProfile

Title: Caching array length can save gas Severity: GAS

Caching the array length is more gas efficient. This is because access to a local variable in solidity is more efficient than query storage / calldata / memory. We recommend to change from:

for (uint256 i=0; i<array.length; i++) { ... }

to:

uint len = array.length  
for (uint256 i=0; i<len; i++) { ... }

    ApprovalFollowModule.sol, toCheck, 128
    InteractionLogic.sol, profileIds, 47
    LensHub.sol, datas.vars, 541
    ApprovalFollowModule.sol, addresses, 41
    PublishingLogic.sol, byteHandle, 403

Title: Prefix increments are cheaper than postfix increments Severity: GAS

Prefix increments are cheaper than postfix increments. Further more, using unchecked {++x} is even more gas efficient, and the gas saving accumulates every iteration and can make a real change There is no risk of overflow caused by increamenting the iteration index in for loops (the ++i in for (uint256 i = 0; i < numIterations; ++i)). But increments perform overflow checks that are not necessary in this case. These functions use not using prefix increments (++x) or not using the unchecked keyword:

    just change to unchecked: PublishingLogic.sol, i, 403
    just change to unchecked: ApprovalFollowModule.sol, i, 128
    just change to unchecked: LensHub.sol, i, 541
    just change to unchecked: ApprovalFollowModule.sol, i, 41
    just change to unchecked: InteractionLogic.sol, i, 47

Title: Unnecessary index init Severity: GAS

In for loops you initialize the index to start from 0, but it already initialized to 0 in default and this assignment cost gas. It is more clear and gas efficient to declare without assigning 0 and will have the same meaning:

    PublishingLogic.sol, 403
    InteractionLogic.sol, 47
    LensHub.sol, 541
    ApprovalFollowModule.sol, 128
    ApprovalFollowModule.sol, 41

Title: Internal functions to private Severity: GAS

The following functions could be set private to save gas and improve code quality:

    FeeModuleBase.sol, _currencyWhitelisted
    FeeModuleBase.sol, _validateDataIsExpected
    ModuleGlobals.sol, _whitelistCurrency
    FollowNFT.sol, _beforeTokenTransfer
    LensHub.sol, _setGovernance
    TimedFeeCollectModule.sol, _processCollectWithReferral
    FeeCollectModule.sol, _processCollect
    LensMultiState.sol, _validateNotPaused
    MockLensHubV2.sol, getRevision
    FollowNFT.sol, _writeSupplySnapshot
    ERC721Time.sol, _burn
    ERC721Time.sol, __ERC721_Init
    ERC721Time.sol, _transfer
    LensHub.sol, _setFollowNFTURI
    PublishingLogic.sol, _emitProfileCreated
    LensHub.sol, _createMirror
    FollowNFT.sol, _delegate
    LimitedFeeCollectModule.sol, _processCollectWithReferral
    ERC721Time.sol, _setOperatorApproval
    LensMultiState.sol, _setState
    VersionedInitializable.sol, getRevision
    LimitedTimedFeeCollectModule.sol, _processCollect
    LensHub.sol, _clearHandleHash
    LimitedFeeCollectModule.sol, _processCollect
    TimedFeeCollectModule.sol, _processCollect
    FeeModuleBase.sol, _treasuryData
    FollowNFT.sol, _writeSnapshot
    LensHub.sol, _validateCallerIsProfileOwnerOrDispatcher
    LensHub.sol, _validateCallerIsProfileOwner
    MockLensHubV2BadRevision.sol, getRevision
    LensHub.sol, _setProfileImageURI
    ModuleGlobals.sol, _setTreasury
    Helpers.sol, getPointedIfMirror
    ERC721Enumerable.sol, _beforeTokenTransfer
    ERC721Time.sol, _baseURI
    ERC721Time.sol, _safeMint
    LensNFTBase.sol, _initialize
    LensHub.sol, _createPost
    LensHub.sol, _createComment
    ERC721Time.sol, _beforeTokenTransfer
    LensNFTBase.sol, _calculateDomainSeparator
    LensHub.sol, _validateCallerIsWhitelistedProfileCreator
    ERC721Time.sol, _approve
    FollowNFT.sol, _moveDelegate
    CollectNFT.sol, _beforeTokenTransfer
    ERC721Time.sol, _exists
    FollowValidationModuleBase.sol, _checkFollowValidity
    ERC721Time.sol, _isApprovedOrOwner
    ERC721Time.sol, _safeTransfer
    ModuleGlobals.sol, _setGovernance
    FeeCollectModule.sol, _processCollectWithReferral
    LensMultiState.sol, _validatePublishingEnabled
    LensNFTBase.sol, _validateRecoveredAddress
    LensHub.sol, _validateCallerIsGovernance
    LensHub.sol, _beforeTokenTransfer
    ERC721Time.sol, _mint
    LensHub.sol, getRevision
    LimitedTimedFeeCollectModule.sol, _processCollectWithReferral
    LensHub.sol, _setDispatcher
    ModuleGlobals.sol, _setTreasuryFee

Title: Public functions to external Severity: GAS

The following functions could be set external to save gas and improve code quality. External call cost is less expensive than of public functions.

    LensNFTBase.sol, burnWithSig
    ERC721Enumerable.sol, tokenByIndex
    LensNFTBase.sol, burn
    LensHub.sol, burn
    ERC721Time.sol, supportsInterface
    ERC721Time.sol, balanceOf
    ERC721Time.sol, symbol
    FollowNFT.sol, tokenURI
    ERC721Enumerable.sol, supportsInterface
    ERC721Time.sol, tokenDataOf
    ERC721Time.sol, setApprovalForAll
    ERC721Time.sol, transferFrom
    ERC721Time.sol, name
    LensHub.sol, tokenURI
    ERC721Enumerable.sol, tokenOfOwnerByIndex
    ERC721Time.sol, mintTimestampOf
    ERC721Time.sol, approve
    ERC721Time.sol, ownerOf
    CollectNFT.sol, tokenURI
    ERC721Time.sol, tokenURI
    LensHub.sol, burnWithSig
    ERC721Enumerable.sol, totalSupply

Title: Unnecessary default assignment Severity: GAS

Unnecessary default assignments, you can just declare and it will save gas and have the same meaning.

    VersionedInitializable.sol (L#29) : uint256 private lastInitializedRevision = 0; 

Title: Rearrange state variables Severity: GAS

You can change the order of the storage variables to decrease memory uses.

In FollowNFT.sol,rearranging the storage fields can optimize to: 5 slots from: 6 slots. The new order of types (you choose the actual variables):

  1. bytes32
  2. uint256
  3. uint256
  4. uint256
  5. address
  6. bool

In CollectNFT.sol,rearranging the storage fields can optimize to: 4 slots from: 5 slots. The new order of types (you choose the actual variables):

  1. uint256
  2. uint256
  3. uint256
  4. address
  5. bool

Title: Short the following require messages Severity: GAS

The following require messages are of length more than 32 and we think are short enough to short them into exactly 32 characters such that it will be placed in one slot of memory and the require function will cost less gas. The list:

    Solidity file: ERC721Time.sol, In line 152, Require message length to shorten: 33, The message: ERC721: approval to current owner
    Solidity file: ERC721Time.sol, In line 396, Require message length to shorten: 36, The message: ERC721: transfer to the zero address

Title: Unused inheritance Severity: GAS

Some of your contract inherent contracts but aren't use them at all.
We recommend not to inherent those contracts.

    ApprovalFollowModule.sol; the inherited contracts FollowValidatorFollowModuleBase not used
    MockLensHubV2BadRevision.sol; the inherited contracts LensNFTBase, VersionedInitializable, LensMultiState not used
    FeeFollowModule.sol; the inherited contracts FollowValidatorFollowModuleBase not used
    MockLensHubV2.sol; the inherited contracts LensNFTBase, VersionedInitializable, LensMultiState not used
    LensNFTBase.sol; the inherited contracts ERC721Enumerable not used
    LensHub.sol; the inherited contracts VersionedInitializable not used
    SecretCodeFollowModule.sol; the inherited contracts FollowValidatorFollowModuleBase not used

Title: Unnecessary constructor Severity: GAS

The following constructors are empty. (A similar issue https://github.com/code-423n4/2021-11-fei-findings/issues/12)

    FeeCollectModule.sol.constructor
    LimitedTimedFeeCollectModule.sol.constructor
    LimitedFeeCollectModule.sol.constructor
    ApprovalFollowModule.sol.constructor
    TimedFeeCollectModule.sol.constructor
    SecretCodeFollowModule.sol.constructor
    FollowerOnlyReferenceModule.sol.constructor
    EmptyCollectModule.sol.constructor
    FeeFollowModule.sol.constructor

Title: Unnecessary functions Severity: GAS

The following functions are not used at all. Therefore you can remove them to save deployment gas and improve code clearness.

    FeeModuleBase.sol, _currencyWhitelisted
    Helpers.sol, getPointedIfMirror
    FeeModuleBase.sol, _validateDataIsExpected
    LensNFTBase.sol, _initialize
    LensMultiState.sol, _setState
    MockLensHubV2.sol, getRevision
    LensHub.sol, getRevision
    ERC721Time.sol, _burn
    ERC721Time.sol, __ERC721_Init
    FeeModuleBase.sol, _treasuryData
    MockLensHubV2BadRevision.sol, getRevision

Title: Use unchecked to save gas for certain additive calculations that cannot overflow Severity: GAS

You can use unchecked in the following calculations since there is no risk to overflow:

    TimedFeeCollectModule.sol (L#71) - uint40 endTimestamp = uint40(block.timestamp) + ONE_DAY; 
    LimitedTimedFeeCollectModule.sol (L#72) - uint40 endTimestamp = uint40(block.timestamp) + ONE_DAY; 

Title: Use calldata instead of memory Severity: GAS

Use calldata instead of memory for function parameters In some cases, having function arguments in calldata instead of memory is more optimal.

    ERC721Time._checkOnERC721Received (_data)
    PublishingLogic.createPost (referenceModuleData)
    ERC721Time._safeMint (_data)
    PublishingLogic._initFollowModule (followModuleData)
    LensHub._createPost (collectModuleData)
    ERC721Time._safeTransfer (_data)
    PublishingLogic._initPubCollectModule (collectModuleData)
    LensHub._createPost (contentURI)
    PublishingLogic.createPost (collectModuleData)
    PublishingLogic._initPubReferenceModule (referenceModuleData)
    PublishingLogic._emitCommentCreated (collectModuleReturnData)
    LensHub._createPost (referenceModuleData)
    PublishingLogic._emitCommentCreated (referenceModuleReturnData)
    PublishingLogic._emitProfileCreated (followModuleReturnData)
    ERC721Time.safeTransferFrom (_data)

Title: Consider inline the following functions to save gas Severity: GAS

You can inline the following functions instead of writing a specific function to save gas.
(see https://github.com/code-423n4/2021-11-nested-findings/issues/167 for a similar issue.)

    ERC721Time.sol, _baseURI, { return ''; }
    MockLensHubV2BadRevision.sol, getRevision, { return REVISION; }
    LensHub.sol, getRevision, { return REVISION; }
    MockLensHubV2.sol, getRevision, { return REVISION; }
    ERC721Time.sol, _exists, { return _tokenData[tokenId].owner != address(0); }
    FeeModuleBase.sol, _treasuryData, { return IModuleGlobals(MODULE_GLOBALS).getTreasuryData(); }
    FeeModuleBase.sol, _currencyWhitelisted, { return IModuleGlobals(MODULE_GLOBALS).isCurrencyWhitelisted(currency); }

Title: Inline one time use functions Severity: GAS

The following functions are used exactly once. Therefore you can inline them and save gas and improve code clearness.

    ERC721Enumerable.sol, _addTokenToAllTokensEnumeration
    ModuleGlobals.sol, _whitelistCurrency
    FollowNFT.sol, _beforeTokenTransfer
    TimedFeeCollectModule.sol, _processCollectWithReferral
    ERC721Enumerable.sol, _removeTokenFromAllTokensEnumeration
    FeeCollectModule.sol, _processCollect
    LensMultiState.sol, _validateNotPaused
    PublishingLogic.sol, _emitProfileCreated
    LimitedFeeCollectModule.sol, _processCollectWithReferral
    ERC721Time.sol, _setOperatorApproval
    VersionedInitializable.sol, getRevision
    LimitedTimedFeeCollectModule.sol, _processCollect
    LimitedFeeCollectModule.sol, _processCollect
    TimedFeeCollectModule.sol, _processCollect
    PublishingLogic.sol, _validateHandle
    ERC721Enumerable.sol, _beforeTokenTransfer
    ERC721Time.sol, _baseURI
    LensHub.sol, _validateCallerIsWhitelistedProfileCreator
    CollectNFT.sol, _beforeTokenTransfer
    FollowValidationModuleBase.sol, _checkFollowValidity
    ERC721Time.sol, _safeTransfer
    PublishingLogic.sol, _emitCommentCreated
    FeeCollectModule.sol, _processCollectWithReferral
    LensMultiState.sol, _validatePublishingEnabled
    ERC721Enumerable.sol, _addTokenToOwnerEnumeration
    LensHub.sol, _validateCallerIsGovernance
    LensHub.sol, _beforeTokenTransfer
    ERC721Time.sol, _mint
    ERC721Enumerable.sol, _removeTokenFromOwnerEnumeration
    LimitedTimedFeeCollectModule.sol, _processCollectWithReferral

Title: Change if -> revert pattern to require Severity: GAS

Change if -> revert pattern to 'require' to save gas and improve code quality, if (some_condition) { revert(revert_message) }

to: require(!some_condition, revert_message)

In the following locations:

    ERC721Time.sol, 452

Title: Never used parameters Severity: GAS / Low

Those are functions and parameters pairs that the function doesn't use the parameter. In case those functions are external/public this is even worst since the user is required to put value that never used and can misslead him and waste its time and also costs GAS.

    FeeFollowModule.sol: function constructor parameter moduleGlobals isn't used. (constructor is default)
    MockReferenceModule.sol: function processComment parameter profileIdPointed isn't used. (processComment is external)
    RevertCollectModule.sol: function processCollect parameter profileId isn't used. (processCollect is external)
    MockFollowModule.sol: function followModuleTransferHook parameter from isn't used. (followModuleTransferHook is external)
    MockFollowModule.sol: function followModuleTransferHook parameter profileId isn't used. (followModuleTransferHook is external)
    RevertCollectModule.sol: function initializePublicationCollectModule parameter data isn't used. (initializePublicationCollectModule is external)
    MockFollowModule.sol: function initializeFollowModule parameter profileId isn't used. (initializeFollowModule is external)
Zer0dot commented 2 years ago

1) Invalid. They are already immutable. 2) Invalid. None of this makes sense and not all of these are even state variables. 3) Valid! Good catch, will implement this. 4) 1. Invalid, 2. Valid, been thinking of a way to do this, since you cannot slap an unchecked {} in a for increment statement, the only way to do this is to have an internal (either in the contract or in a library so you can just i.increment(). 5) Valid! Will implement this. 6) Invalid, setting private functions internal does not save gas or affect anything except inheritance. 7) Valid, but since these are basically all basic NFT functions (except the public burn() & burnWithSig() which will be changed) I opt not to modify them. 8) Valid, will change. 9) Invalid, some of these are immutables and don't take a storage slot. 10) Invalid, we opt to keep the standard ERC721 revert messages. 11) Invalid, they are used, not sure what this means. 12) Invalid, the constructors construct inherited contracts. 13) Invalid... They are used in inheriting contracts :( 14) Valid! Good catch, will implement. 15) Invalid, doing so leads to a stack too deep error or would change basic ERC721 functionality. 16) Valid-ish, most of this is because we want to keep a clean interface that can be built upon and maintain readable code. 17) Valid-ish, again mostly this is because we want to keep a clean readable codebase and don't want to change the standard ERC721 too much. 18) Invalid, as far as I know modern errors save code size and gas on reverts, especially if the condition does not require a negation, where it would in the case of a require (extra NOT opcode). 19) Invalid, these are done this way to match the standard defined interfaces.

0xleastwood commented 2 years ago

While a couple of these issues are valid, its obvious that the warden has used automated tooling and has not taken the time to validate the output of any issues raised. Please stop doing this, you are wasting the sponsor's time and judge's time with this kind of behaviour. To make it clear for this warden and others, I will not reward submissions which are directly sourced from automated tooling and are without proper context and detail. As such I'm marking this as invalid.

Zer0dot commented 2 years ago

While a couple of these issues are valid, its obvious that the warden has used automated tooling and has not taken the time to validate the output of any issues raised. Please stop doing this, you are wasting the sponsor's time and judge's time with this kind of behaviour. To make it clear for this warden and others, I will not reward submissions which are directly sourced from automated tooling and are without proper context and detail. As such I'm marking this as invalid.

Gigachad answer