FactoryDAO contest - robee's results

The DAO that builds DAOs.

General Information

Platform: Code4rena

Start Date: 04/05/2022

Pot Size: $50,000 DAI

Total HM: 24

Participants: 71

Period: 5 days

Judge: Justin Goro

Total Solo HM: 14

Id: 119

League: ETH

FactoryDAO

Findings Distribution

Researcher Performance

Rank: 24/71

Findings: 2

Award: $281.22

🌟 Selected for report: 0

🚀 Solo Findings: 0

Duplicates in array

You allow in some arrays to have duplicates. Sometimes you assumes there are no duplicates in the array.

Code instance:

PermissionlessBasicPoolFactory.addPool pushed (rewardTokenAddresses)

Solidity compiler versions mismatch

The project is compiled with different versions of solidity, which is not recommended because it can lead to undefined behaviors.

Code instance:

Not verified owner

owner param should be validated to make sure the owner address is not address(0). Otherwise if not given the right input all only owner accessible functions will be unaccessible.

Code instances:

VoterID.sol.createIdentityFor thisOwner VoterID.sol.setOwner newOwner

Named return issue

Users can mistakenly think that the return value is the named return, but it is actually the actualreturn statement that comes after. To know that the user needs to read the code and is confusing. Furthermore, removing either the actual return or the named return will save gas.

Code instances:

SpeedBumpPriceGate.sol, getCost MerkleEligibility.sol, addGate FixedPricePassThruGate.sol, getCost MerkleEligibility.sol, isEligible

Two Steps Verification before Transferring Ownership

The following contracts have a function that allows them an admin to change it to a different address. If the admin accidentally uses an invalid address for which they do not have the private key, then the system gets locked. It is important to have two steps admin change where the first is announcing a pending new admin and the new address should then claim its ownership. A similar issue was reported in a previous contest and was assigned a severity of medium: code-423n4/2021-06-realitycards-findings#105

Code instances:

VoterID.sol MerkleIdentity.sol

Never used parameters

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.

Code instance:

VoterID.sol: function isContract parameter account isn't used. (isContract is internal)

Missing commenting

The following functions are missing commenting as describe below:

Code instances:

MerkleIdentity.sol, verifyMetadata (public), @return is missing MerkleIdentity.sol, getPrice (public), parameter merkleIndex not commented

Check transfer receiver is not 0 to avoid burned money

Transferring tokens to the zero address is usually prohibited to accidentally avoid "burning" tokens by sending them to an unrecoverable zero address.

Code instances:

https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/MerkleResistor.sol#L114 https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/MerkleVesting.sol#L87 https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/PermissionlessBasicPoolFactory.sol#L143 https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/MerkleResistor.sol#L195 https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/VoterID.sol#L142

Never used parameters

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.

Code instance:

VoterID.sol: function isContract parameter account isn't used. (isContract is internal)

Missing commenting

The following functions are missing commenting as describe below:

Code instances:

MerkleIdentity.sol, verifyMetadata (public), @return is missing MerkleIdentity.sol, getPrice (public), parameter merkleIndex not commented

Add a timelock

To give more trust to users: functions that set key/critical variables should be put behind a timelock.

Code instances:

https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/VoterID.sol#L162 https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/MerkleIdentity.sol#L75 https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/PermissionlessBasicPoolFactory.sol#L314 https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/MerkleIdentity.sol#L60 https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/VoterID.sol#L251

Div by 0

Division by 0 can lead to accidentally revert, (An example of a similar issue - https://github.com/code-423n4/2021-10-defiprotocol-findings/issues/84)

Code instances:

https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/MerkleVesting.sol#L117 startTime, endTime might be 0 https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/SpeedBumpPriceGate.sol#L72 price might be 0 https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/MerkleResistor.sol#L264 coinsPerSecond might be 0

Tokens with fee on transfer are not supported

There are ERC20 tokens that charge fee for every transfer() / transferFrom().

Vault.sol#addValue() assumes that the received amount is the same as the transfer amount, and uses it to calculate attributions, balance amounts, etc. But, the actual transferred amount can be lower for those tokens. Therefore it's recommended to use the balance change before and after the transfer instead of the amount. This way you also support the tokens with transfer fee - that are popular.

Code instance:

https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/PermissionlessBasicPoolFactory.sol#L143

Not verified input

external / public functions parameters should be validated to make sure the address is not 0. Otherwise if not given the right input it can mistakenly lead to loss of user funds.

Code instances:

MerkleIdentity.sol.setTreeAdder newAdder MerkleIdentity.sol.addMerkleTree nftAddress MerkleIdentity.sol.addMerkleTree eligibilityAddress SpeedBumpPriceGate.sol.addGate beneficiary VoterID.sol.approve approved

#0 - illuzen

2022-05-10T08:51:05Z

Zero address checks are not in scope. Fee token issue is duplicate. Div by 0 issues are user mis-entering function parameters and are out of scope Timelock is partially duplicate, but wider in scope, valid Missing comment is valid Unused arguments is not valid, read the code Duplicates in array is not valid, tokens are not assumed to be unique Others are duplicates

State variables that could be set immutable

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

Code instances:

_name in VoterID.sol _minter in VoterID.sol _symbol in VoterID.sol globalBeneficiary in PermissionlessBasicPoolFactory.sol gateMaster in MerkleEligibility.sol

Caching array length can save 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++) { ... }

Code instances:

PermissionlessBasicPoolFactory.sol, rewards, 224 MerkleLib.sol, proof, 22 PermissionlessBasicPoolFactory.sol, rewardTokens.pool, 266 PermissionlessBasicPoolFactory.sol, rewardFunding.pool, 141 PermissionlessBasicPoolFactory.sol, rewardTokenAddresses, 115

Prefix increments are cheaper than postfix increments

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:

Code instances:

change to prefix increment and unchecked: PermissionlessBasicPoolFactory.sol, i, 249 change to prefix increment and unchecked: PermissionlessBasicPoolFactory.sol, i, 168 change to prefix increment and unchecked: PermissionlessBasicPoolFactory.sol, i, 266 change to prefix increment and unchecked: PermissionlessBasicPoolFactory.sol, i, 115 change to prefix increment and unchecked: PermissionlessBasicPoolFactory.sol, i, 224

Unnecessary index init

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:

Code instances:

PermissionlessBasicPoolFactory.sol, 249 MerkleLib.sol, 22 PermissionlessBasicPoolFactory.sol, 224 PermissionlessBasicPoolFactory.sol, 168 PermissionlessBasicPoolFactory.sol, 266

Storage double reading. Could save SLOAD

Reading a storage variable is gas costly (SLOAD). In cases of multiple read of a storage variable in the same scope, caching the first read (i.e saving as a local variable) can save gas and decrease the overall gas uses. The following is a list of functions and the storage variables that you read twice:

Code instances:

PermissionlessBasicPoolFactory.sol: globalBeneficiary is read twice in setGlobalTax MerkleIdentity.sol: treeAdder is read twice in addMerkleTree

Unnecessary default assignment

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

Code instances:

MerkleDropFactory.sol (L#17) : uint public numTrees = 0; MerkleEligibility.sol (L#31) : uint public numGates = 0; VoterID.sol (L#69) : uint public numIdentities = 0; MerkleVesting.sol (L#16) : uint public numTrees = 0; MerkleResistor.sol (L#24) : uint public numTrees = 0;

Rearrange state variables

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

Code instance:

In VoterID.sol,rearranging the storage fields can optimize to: 5 slots from: 6 slots. The new order of types (you choose the actual variables): 1. string 2. string 3. uint 4. address 5. bytes4 6. bytes4 7. bytes4 8. address 9. bytes4 10. bytes4

Short the following require messages

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:

Code instances:

Solidity file: MerkleDropFactory.sol, In line 90, Require message length to shorten: 35, The message: Provided merkle index doesn't exist Solidity file: PermissionlessBasicPoolFactory.sol, In line 315, Require message length to shorten: 34, The message: Only globalBeneficiary can set tax Solidity file: VoterID.sol, In line 239, Require message length to shorten: 35, The message: Identity: Not authorized to approve Solidity file: MerkleResistor.sol, In line 171, Require message length to shorten: 39, The message: You must initialize your account first. Solidity file: MerkleResistor.sol, In line 136, Require message length to shorten: 36, The message: Can only initialize your own tranche

Use unchecked to save gas for certain additive calculations that cannot overflow

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

Code instances:

MerkleResistor.sol (L#264) - uint startTime = block.timestamp + vestingTime - (totalCoins / coinsPerSecond); MerkleResistor.sol (L#153) - tranches[destination][treeIndex] = Tranche( totalCoins, totalCoins, startTime, block.timestamp + vestingTime, coinsPerSecond, startTime );

Inline one time use functions

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

Code instances:

VoterID.sol, isContract VoterID.sol, checkOnERC721Received VoterID.sol, transfer

Do not cache msg.sender

We recommend not to cache msg.sender since calling it is 2 gas while reading a variable is more.

Code instance:

https://github.com/code-423n4/2022-05-factorydao/tree/main/contracts/PermissionlessBasicPoolFactory.sol#L193

#0 - illuzen

2022-05-10T08:55:32Z

msg.sender cache: valid inline functions: valid unchecked: duplicate short messages: duplicate rearrange state: valid default assignment: duplicate storage double reading: valid all others duplicates

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter