L115/141/168/224/266: Gas would be saved if instead of i++, it was ++i without marking.
L115/141/168/224/266: Gas would be saved if i = 0 is not set.
L166: It is not possible for nowish to be smaller than receipt.timeDeposited, since if or if deposit has to be executed first so that it does not reverse the require of lines 159 and 160.
L168: It would be better to use rewardsLocal.length, instead of pool.rewardsWeiPerSecondPerToken.length, since the variable in memory generates less gas cost than in storage (600 gas less, in remix tests).
MerkleLib.sol
L22: You could save 940 gas if instead of i += 1 in the for, you put unchecked { ++i;}
L22: Gas would be saved if i = 0 is not set.
MerkleDropFactory.sol
L17:Gas is saved if numTrees is not set to any value.
MerkleVesting.sol
L16: Gas is saved if numTrees is not set to any value.
L104: It should be that in the initialize() function startTime is smaller than endTime. It should also be validated that endTime != startTime, otherwise it would revert to line 117 uint coinsPerSecond = totalCoins / (endTime - startTime);
L150: Gas is saved if currentWithdrawal is not set to any value.
MerkleResistor.sol
L24: Gas is saved if numTrees is not set to any value.
L176: Gas is saved if currentWithdrawal is not set to any value.
MerkleIdentity.sol
L124: In the withdraw() function, a validation of an input parameter is performed in the middle of the function, it would be more optimal to do it at the beginning, since if it reverts, less gas is spent than it is now.
L127: In the withdraw() function, a validation is performed with verifyMetadata(), it would be more optimal to do it just after the variable tree = merkleTrees[merkleIndex] is read from storage; since if it reverses, less gas is spent than it is now.
L142/L143: You could save a little gas, if instead of returning a variable directly, you return the function call, in the getPrice() function.
L118/L121: In the withdraw() function, the local variable id is only used once, therefore, it would be less expensive and more explicit (since the name "id" is not at all descriptive) it would be to do it like this: IVoterID(tree.nftAddress ).createIdentityFor(msg.sender, tokenId, uri);
MerkleEligibility.sol
L31: Gas is saved if numTrees is not set to any value.
VoterID.sol
L69: Gas is saved if numTrees is not set to any value.
L97: The ownerOnly() modifier could be a view function and that way you could save a lot of gas (20,000 gas tested in remix).
PermissionlessBasicPoolFactory.sol
MerkleLib.sol
MerkleDropFactory.sol
MerkleVesting.sol
MerkleResistor.sol
MerkleIdentity.sol
L124: In the withdraw() function, a validation of an input parameter is performed in the middle of the function, it would be more optimal to do it at the beginning, since if it reverts, less gas is spent than it is now.
L127: In the withdraw() function, a validation is performed with verifyMetadata(), it would be more optimal to do it just after the variable tree = merkleTrees[merkleIndex] is read from storage; since if it reverses, less gas is spent than it is now.
L142/L143: You could save a little gas, if instead of returning a variable directly, you return the function call, in the getPrice() function.
L118/L121: In the withdraw() function, the local variable id is only used once, therefore, it would be less expensive and more explicit (since the name "id" is not at all descriptive) it would be to do it like this: IVoterID(tree.nftAddress ).createIdentityFor(msg.sender, tokenId, uri);
MerkleEligibility.sol
VoterID.sol