It is recommended to decrease revert messages to maximum 32 bytes. Alternatively custom error types should be used.
3. Use custom errors instead of revert strings to save gas
Impact
Usage of Custom Errors reduces the gas cost.
Proof of Concept
PermissionlessBasicPoolFactory.sol:
require(rewardsWeiPerSecondPerToken.length == rewardTokenAddresses.length, 'Rewards and reward token arrays must be same length');
require(success, 'Token deposits failed');
require(pool.id == poolId, 'Uninitialized pool');
require(receipt.id == receiptId, 'Uninitialized receipt');
require(pool.id == poolId, 'Uninitialized pool');
require(block.timestamp > pool.startTime, 'Cannot deposit before pool start');
require(block.timestamp < pool.endTime, 'Cannot deposit after pool ends');
require(pool.totalDepositsWei < pool.maximumDepositWei, 'Maximum deposit already reached');
require(success, 'Token transfer failed');
require(pool.id == poolId, 'Uninitialized pool');
require(receipt.id == receiptId, 'Can only withdraw real receipts');
require(receipt.owner == msg.sender || block.timestamp > pool.endTime, 'Can only withdraw your own deposit');
require(receipt.timeWithdrawn == 0, 'Can only withdraw once per receipt');
require(success, 'Token transfer failed');
require(pool.id == poolId, 'Uninitialized pool');
require(pool.totalDepositsWei == 0, 'Cannot withdraw until all deposits are withdrawn');
require(block.timestamp > pool.endTime, 'Contract must reach maturity');
require(success, 'Token transfer failed');
require(pool.id == poolId, 'Uninitialized pool');
require(success, 'Token transfer failed');
require(msg.sender == globalBeneficiary, 'Only globalBeneficiary can set tax');
require(newTaxPerCapita < 1000, 'Tax too high');
MerkleDropFactory.sol:
require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed");
require(treeIndex <= numTrees, "Provided merkle index doesn't exist");
require(!withdrawn[destination][treeIndex], "You have already withdrawn your entitled token.");
require(tree.merkleRoot.verifyProof(leaf, proof), "The proof could not be verified.");
require(IERC20(tree.tokenAddress).transfer(destination, value), "ERC20 transfer failed");
MerkleVesting.sol:
require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed");
require(!initialized[destination][treeIndex], "Already initialized");
require(tree.rootHash.verifyProof(leaf, proof), "The proof could not be verified.");
require(initialized[destination][treeIndex], "You must initialize your account first.");
require(block.timestamp > tranche.lockPeriodEndTime, 'Must wait until after lock period');
require(tranche.currentCoins > 0, 'No coins left to withdraw');
MerkleResistor.sol:
require(pctUpFront < 100, 'pctUpFront >= 100');
require(minEndTime < maxEndTime, 'minEndTime must be less than maxEndTime');
require(IERC20(merkleTree.tokenAddress).transferFrom(msg.sender, address(this), value), "ERC20 transfer failed");
require(msg.sender == destination, 'Can only initialize your own tranche');
require(!initialized[destination][treeIndex], "Already initialized");
require(tree.merkleRoot.verifyProof(leaf, proof), "The proof could not be verified.");
require(valid, 'Invalid vesting schedule');
require(initialized[destination][treeIndex], "You must initialize your account first.");
require(tranche.currentCoins > 0, 'No coins left to withdraw');
require(IERC20(tree.tokenAddress).transfer(destination, currentWithdrawal), 'Token transfer failed');
FixedPricePassThruGate.sol:
require(msg.value >= gate.ethCost, 'Please send more ETH');
require(sent, 'ETH transfer failed')
SpeedBumpPriceGate.sol:
require(msg.value >= price, 'Please send more ETH');
require(sent, 'ETH transfer failed');
MerkleEligibility.sol:
require(msg.sender == gateMaster, "Only gatemaster may call this.");
require(isEligible(index, recipient, proof), "Address is not eligible");
MerkleIdentity.sol:
require (msg.sender == management, 'Only management may call this');
require(msg.sender == treeAdder, 'Only treeAdder can add trees');
require(merkleIndex <= numTrees, 'merkleIndex out of range');
require(verifyMetadata(tree.metadataMerkleRoot, tokenId, uri, metadataProof), "The metadata proof could not be verified");
VoterID.sol:
require (msg.sender == _owner_, 'Identity: Only owner may call this');
require(msg.sender == _minter, 'Only minter may create identity');
require(owners[thisToken] == address(0), 'Token already exists');
require(ooner != address(0), 'No such token');
require(isApproved(msg.sender, tokenId), 'Identity: Unapproved transfer');
require(checkOnERC721Received(from, to, tokenId, data), "Identity: transfer to non ERC721Receiver implementer");
require(isApproved(msg.sender, tokenId), 'Identity: Not authorized to approve');
require(holder != approved, 'Identity: Approving self not allowed');
require(holder != address(0), 'Identity: Invalid tokenId');
require(owners[tokenId] == from, "Identity: Transfer of token that is not own");
require(to != address(0), "Identity: transfer to the zero address");
require(_index < numIdentities, 'Invalid token index');
require(_index < balances[_address], 'Index out of range');
require(_address != address(0), 'Cannot query zero address');
Tools Used
Manual Review / VSCode
Recommended Mitigation Steps
It is recommended to add Custom Errors.
4. ++i/--i costs less gas compared to i++, i += 1, i-- or i -= 1
Impact
++i or --i costs less gas compared to i++, i += 1, i-- or i -= 1 for unsigned integer as pre-increment/pre-decrement is cheaper (about 5 gas per iteration).
It is recommended to use ++i or --i instead of i++, i += 1, i-- or i -= 1 to increment value of an unsigned integer variable.
5. Obsolete overflow/underflow check
Impact
Starting from solidity 0.8.0 there is built-in check for overflows/underflows. This mechanism automatically checks if the variable overflows or underflows and throws an error. Multiple contracts use increments that cannot overflow but consume additional gas for checks.
It is recommended wrap incrementing of i with unchecked block:
for (uint256 i; i < numWarriors; ) {
_mint(msg.sender);
unchecked { ++i };
}
6. No need to explicitly initialize variables with default values
Impact
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for addresses). Explicitly initializing it with its default value is an anti-pattern and waste of gas.
1. Cache array length outside of loop
Impact
Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop.
Proof of Concept
PermissionlessBasicPoolFactory
MerkleLib.sol
:Tools Used
Manual Review / VSCode
Recommended Mitigation Steps
It is recommended to cache the array length outside of for loop.
2. Long revert error messages
Impact
Shortening revert error messages to fit in 32 bytes will decrease gas costs for deployment and gas costs when the revert condition has been met.
Proof of Concept
PermissionlessBasicPoolFactory.sol
:MerkleDropFactory.sol
:MerkleResistor.sol
:MerkleVesting.sol
:MerkleIdentity.sol
:VoterID.sol
:Tools Used
Manual Review / VSCode
Recommended Mitigation Steps
It is recommended to decrease revert messages to maximum 32 bytes. Alternatively custom error types should be used.
3. Use custom errors instead of revert strings to save gas
Impact
Usage of Custom Errors reduces the gas cost.
Proof of Concept
PermissionlessBasicPoolFactory.sol
:MerkleDropFactory.sol
:MerkleVesting.sol
:MerkleResistor.sol
:FixedPricePassThruGate.sol
:SpeedBumpPriceGate.sol
:MerkleEligibility.sol
:MerkleIdentity.sol
:VoterID.sol
:Tools Used
Manual Review / VSCode
Recommended Mitigation Steps
It is recommended to add Custom Errors.
4. ++i/--i costs less gas compared to i++, i += 1, i-- or i -= 1
Impact
++i
or--i
costs less gas compared toi++
,i += 1
,i--
ori -= 1
for unsigned integer as pre-increment/pre-decrement is cheaper (about 5 gas per iteration).Proof of Concept
PermissionlessBasicPoolFactory.sol
:MerkleLib.sol
:MerkleEligibility.sol
:VoterID.sol
:Tools Used
Manual Review / VSCode
Recommended Mitigation Steps
It is recommended to use
++i
or--i
instead ofi++
,i += 1
,i--
ori -= 1
to increment value of an unsigned integer variable.5. Obsolete overflow/underflow check
Impact
Starting from solidity
0.8.0
there is built-in check for overflows/underflows. This mechanism automatically checks if the variable overflows or underflows and throws an error. Multiple contracts use increments that cannot overflow but consume additional gas for checks.Proof of Concept
PermissionlessBasicPoolFactory.sol
:MerkleLib.sol
:i += 1
can be unchecked - https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleLib.sol#L22MerkleDropFactory.sol
:++numTrees
can be unchecked - https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleDropFactory.sol#L51MerkleResistor.sol
:++numTrees
can be unchecked - https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleResistor.sol#L86MerkleVesting.sol
:++numTrees
can be unchecked - https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleVesting.sol#L64FixedPricePassThruGate.sol
:++numGates
can be unchecked - https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/FixedPricePassThruGate.sol#L30SpeedBumpPriceGate.sol
:++numGates
can be unchecked - https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/SpeedBumpPriceGate.sol#L39MerkleEligibility.sol
:++numGates
can be unchecked - https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleEligibility.sol#L47++timesWithdrawn[index][recipient]
can be unchecked - https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleEligibility.sol#L91++gate.totalWithdrawals
can be unchecked - https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleEligibility.sol#L93MerkleIdentity.sol
:++numTrees
can be unchecked - https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleIdentity.sol#L98Tools Used
Manual Review / VSCode
Recommended Mitigation Steps
It is recommended wrap incrementing of
i
withunchecked
block:6. No need to explicitly initialize variables with default values
Impact
If a variable is not set/initialized, it is assumed to have the default value (
0
for uint,false
for bool,address(0)
for addresses). Explicitly initializing it with its default value is an anti-pattern and waste of gas.Proof of Concept
PermissionlessBasicPoolFactory.sol
MerkleLib.sol
:MerkleDropFactory.sol
:MerkleResistor.sol
:MerkleVesting.sol
:MerkleEligibility.sol
:VoterID.sol
:Tools Used
Manual Review / VSCode
Recommended Mitigation Steps
It is recommended to remove explicit initializations with default values.
7. Pack integer values
Impact
Proof of Concept
PermissionlessBasicPoolFactory.sol
:timeDeposited
andtimeWithdrawn
asuint64
- https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/PermissionlessBasicPoolFactory.sol#L18-L19startTime
andendTime
asuint64
- https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/PermissionlessBasicPoolFactory.sol#L32-L33taxPerCapita
asuint16
- https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/PermissionlessBasicPoolFactory.sol#L34MerkleResistor.sol
:startTime
,endTime
,lastWithdrawalTime
asuint64
- https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleResistor.sol#L27-L34minEndTime
,maxEndTime
asuint64
- https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleResistor.sol#L37-L45pctUpFron
asuint8
- https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleResistor.sol#L42MerkleVesting.sol
:startTime
,endTime
,lastWithdrawalTime
,lockPeriodEndTime
asuint64
- https://github.com/code-423n4/2022-05-factorydao/blob/db415804c06143d8af6880bc4cda7222e5463c0e/contracts/MerkleVesting.sol#L22-L23Tools Used
Manual Review / VSCode
Recommended Mitigation Steps
It is recommended to pack listed values in order to consume less storage and thus gas.