Platform: Code4rena
Start Date: 12/09/2022
Pot Size: $75,000 USDC
Total HM: 19
Participants: 110
Period: 7 days
Judge: HardlyDifficult
Total Solo HM: 9
Id: 160
League: ETH
Rank: 41/110
Findings: 2
Award: $117.97
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Lambda
Also found by: 0x1f8b, 0x4non, 0x52, 0x5rings, 0xDanielC, 0xNazgul, 0xSmartContract, 0xbepresent, Anth3m, Aymen0909, B2, CRYP70, CertoraInc, Ch_301, Chom, ChristianKuri, CodingNameKiki, Deivitto, Funen, JC, JansenC, Jeiwan, KIntern_NA, MasterCookie, MiloTruck, Olivierdem, PaludoX0, R2, RaymondFam, ReyAdmirado, StevenL, The_GUILD, Tomo, Trust, V_B, __141345__, asutorufos, ayeslick, bin2chen, brgltd, bulej93, c3phas, cccz, ch0bu, cryptphi, csanuragjain, d3e4, delfin454000, djxploit, erictee, fatherOfBlocks, gogo, hansfriese, indijanc, ladboy233, leosathya, lukris02, malinariy, martin, pedr02b2, pfapostol, rvierdiiev, slowmoses, smiling_heretic, tnevler, wagmi
82.3416 USDC - $82.34
Issue | Risk | Instances | |
---|---|---|---|
1 | constructor and transferMultiSig function missing zero address checks inside Globals contract | Low | 2 |
2 | require should be used instead of assert | Low | 5 |
3 | Non-library/Non-interface files should use fixed compiler versions, not floating ones | NC | 21 |
4 | Non-assembly method available | NC | 1 |
5 | Named return variables not used anywhere in the function | NC | 18 |
6 | constant should be defined rather than using magic numbers | NC | 13 |
constructor
and transferMultiSig
function missing zero address checks inside Globals
contract :the constructor
and transferMultiSig
function inside the Globals
contract are missing non zero address checks when setting the multiSig
address which can cause a set/transfer of multiSig
to address(0)
.
Instances include:
File: contracts/globals/Globals.sol
Add non-zero address checks in the instances aforementioned.
require
should be used instead of assert
:Prior to solidity version 0.8.0, hitting an assert consumes the remainder of the transaction’s available gas rather than returning it, as require()/revert()
do. assert()
should be avoided even past solidity version 0.8.0 as its documentation states that “The assert function creates an error of type Panic(uint256). … Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix”.
Instances include:
File: contracts/distribution/TokenDistributor.sol
assert(tokenType == TokenType.Erc20);
assert(tokenType == TokenType.Erc20);
File: contracts/party/PartyGovernance.sol
assert(tokenType == ITokenDistributor.TokenType.Erc20);
File: contracts/proposals/FractionalizeProposal.sol
assert(vault.balanceOf(address(this)) == supply);
File: contracts/proposals/ProposalExecutionEngine.sol
assert(currentInProgressProposalId == 0);
Replace the assert
checks aforementioned with require
statements.
Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively.
check this source : https://swcregistry.io/docs/SWC-103
All contracts use a floating solidity version :
pragma solidity ^0.8;
Lock the pragma version to the same version as used in the other contracts and also consider known bugs (https://github.com/ethereum/solidity/releases) for the compiler version that is chosen.
Pragma statements can be allowed to float when a contract is intended for consumption by other developers, as in the case with contracts in a library or EthPM package. Otherwise, the developer would need to manually update the pragma in order to compile it locally.
<address>.code.length
can be used in Solidity >= 0.8.0
to access an account’s code size and check if it is a contract without inline assembly.
There is 1 instance of this issue:
File: contracts/utils/Implementation.sol
assembly { codeSize := extcodesize(address()) }
Assembly method can be replaced it with :
codeSize = address().code.length;
When Named return variable are declared they should be used inside the function instead of the return statement or if not they should be removed to avoid confusion.
Instances include:
File: contracts/crowdfund/CrowdfundFactory.sol
returns (bytes12 newGateKeeperId)
File: contracts/distribution/TokenDistributor.sol
File: contracts/party/PartyGovernance.sol
returns (ITokenDistributor.DistributionInfo memory distInfo)
returns (VotingPowerSnapshot memory snap)
returns (ProposalStatus status)
File: contracts/party/PartyGovernanceNFT.sol
File: contracts/proposals/ArbitraryCallsProposal.sol
returns (bytes memory nextProgressData)
File: contracts/proposals/FractionalizeProposal.sol
returns (bytes memory nextProgressData)
File: contracts/proposals/ListOnZoraProposal.sol
returns (bytes memory nextProgressData)
File: contracts/proposals/ProposalExecutionEngine.sol
returns (bytes memory nextProgressData)
Either use the named return variables inplace of the return statement or remove them.
constant
should be defined rather than using magic numbers :It is best practice to use constant variables rather than hex/numeric literal values to make the code easier to understand and maintain.
Instances include:
File: contracts/crowdfund/Crowdfund.sol
File: contracts/party/PartyGovernance.sol
File: contracts/proposals/ArbitraryCallsProposal.sol
Replace the hex/numeric literals aforementioned with constants
🌟 Selected for report: CertoraInc
Also found by: 0x1f8b, 0x4non, 0x5rings, 0x85102, 0xNazgul, 0xSmartContract, 0xkatana, Amithuddar, Aymen0909, B2, Bnke0x0, CRYP70, Chom, ChristianKuri, CodingNameKiki, Deivitto, Diraco, Fitraldys, Funen, IgnacioB, JAGADESH, JC, Lambda, LeoS, Matin, Metatron, MiloTruck, Noah3o6, Ocean_Sky, Olivierdem, PaludoX0, RaymondFam, ReyAdmirado, Rohan16, Rolezn, Saintcode_, Sm4rty, SnowMan, StevenL, Tomio, Tomo, V_B, Waze, __141345__, ajtra, asutorufos, aysha, brgltd, bulej93, c3phas, ch0bu, d3e4, delfin454000, dharma09, djxploit, erictee, fatherOfBlocks, francoHacker, gianganhnguyen, gogo, got_targ, ignacio, jag, karanctf, ladboy233, leosathya, lukris02, m_Rassska, malinariy, martin, natzuu, pashov, peanuts, peiw, pfapostol, prasantgupta52, robee, simon135, slowmoses, sryysryy, tnevler
35.6296 USDC - $35.63
Issue | Instances | |
---|---|---|
1 | Variables inside struct should be packed to save gas | 3 |
2 | Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead | 6 |
3 | Using bool for storage incurs overhead | 3 |
4 | array.length should not be looked up in every loop in a for loop | 9 |
5 | Use of ++i cost less gas than i++ in for loops | 1 |
6 | ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as in the case when used in for & while loops | 11 |
7 | It costs more gas to initialize variables to zero than to let the default of zero be applied | 10 |
8 | X += Y/X -= Y costs more gas than X = X + Y/X = X - Y for state variables | 2 |
struct
should be packed to save gas :As the solidity EVM works with 32 bytes, variables less than 32 bytes should be packed inside a struct so that they can be stored in the same slot, this saves gas when writing to storage.
There are 3 instances of this issue:
File: contracts/crowdfund/BuyCrowdfund.sol 19 struct BuyCrowdfundOptions { // The name of the crowdfund. // This will also carry over to the governance party. string name; // The token symbol for both the crowdfund and the governance NFTs. string symbol; // The ERC721 contract of the NFT being bought. IERC721 nftContract; // ID of the NFT being bought. uint256 nftTokenId; // How long this crowdfund has to bid on the NFT, in seconds. uint40 duration; // Maximum amount this crowdfund will pay for the NFT. // If zero, no maximum. uint96 maximumPrice; // An address that receives a portion of the final voting power // when the party transitions into governance. address payable splitRecipient; // What percentage (in bps) of the final total voting power `splitRecipient` // receives. uint16 splitBps; // If ETH is attached during deployment, it will be interpreted // as a contribution. This is who gets credit for that contribution. address initialContributor; // If there is an initial contribution, this is who they will delegate their // voting power to when the crowdfund transitions to governance. address initialDelegate; // The gatekeeper contract to use (if non-null) to restrict who can // contribute to this crowdfund. IGateKeeper gateKeeper; // The gate ID within the gateKeeper contract to use. bytes12 gateKeeperId; // Fixed governance options (i.e. cannot be changed) that the governance // `Party` will be created with if the crowdfund succeeds. FixedGovernanceOpts governanceOpts; }
This should be rearranged as follow to save gas :
struct BuyCrowdfundOptions { // The name of the crowdfund. // This will also carry over to the governance party. string name; // The token symbol for both the crowdfund and the governance NFTs. string symbol; // The ERC721 contract of the NFT being bought. IERC721 nftContract; // ID of the NFT being bought. uint256 nftTokenId; // How long this crowdfund has to bid on the NFT, in seconds. uint40 duration; // Maximum amount this crowdfund will pay for the NFT. // If zero, no maximum. uint96 maximumPrice; // What percentage (in bps) of the final total voting power `splitRecipient` // receives. uint16 splitBps; // An address that receives a portion of the final voting power // when the party transitions into governance. address payable splitRecipient; // If ETH is attached during deployment, it will be interpreted // as a contribution. This is who gets credit for that contribution. address initialContributor; // If there is an initial contribution, this is who they will delegate their // voting power to when the crowdfund transitions to governance. address initialDelegate; // The gatekeeper contract to use (if non-null) to restrict who can // contribute to this crowdfund. IGateKeeper gateKeeper; // The gate ID within the gateKeeper contract to use. bytes12 gateKeeperId; // Fixed governance options (i.e. cannot be changed) that the governance // `Party` will be created with if the crowdfund succeeds. FixedGovernanceOpts governanceOpts; }
The second instance :
File: contracts/crowdfund/BuyCrowdfundBase.sol 20 struct BuyCrowdfundBaseOptions { // The name of the crowdfund. // This will also carry over to the governance party. string name; // The token symbol for both the crowdfund and the governance NFTs. string symbol; // How long this crowdfund has to bid on the NFT, in seconds. uint40 duration; // Maximum amount this crowdfund will pay for the NFT. // If zero, no maximum. uint96 maximumPrice; // An address that receieves an extra share of the final voting power // when the party transitions into governance. address payable splitRecipient; // What percentage (in bps) of the final total voting power `splitRecipient` // receives. uint16 splitBps; // If ETH is attached during deployment, it will be interpreted // as a contribution. This is who gets credit for that contribution. address initialContributor; // If there is an initial contribution, this is who they will delegate their // voting power to when the crowdfund transitions to governance. address initialDelegate; // The gatekeeper contract to use (if non-null) to restrict who can // contribute to this crowdfund. IGateKeeper gateKeeper; // The gatekeeper contract to use (if non-null). bytes12 gateKeeperId; // Governance options. FixedGovernanceOpts governanceOpts; }
This should be rearranged as follow to save gas :
struct BuyCrowdfundBaseOptions { // The name of the crowdfund. // This will also carry over to the governance party. string name; // The token symbol for both the crowdfund and the governance NFTs. string symbol; // How long this crowdfund has to bid on the NFT, in seconds. uint40 duration; // Maximum amount this crowdfund will pay for the NFT. // If zero, no maximum. uint96 maximumPrice; // What percentage (in bps) of the final total voting power `splitRecipient` // receives. uint16 splitBps; // An address that receieves an extra share of the final voting power // when the party transitions into governance. address payable splitRecipient; // If ETH is attached during deployment, it will be interpreted // as a contribution. This is who gets credit for that contribution. address initialContributor; // If there is an initial contribution, this is who they will delegate their // voting power to when the crowdfund transitions to governance. address initialDelegate; // The gatekeeper contract to use (if non-null) to restrict who can // contribute to this crowdfund. IGateKeeper gateKeeper; // The gatekeeper contract to use (if non-null). bytes12 gateKeeperId; // Governance options. FixedGovernanceOpts governanceOpts; }
The third instance :
File: contracts/crowdfund/CollectionBuyCrowdfund.sol 20 struct CollectionBuyCrowdfundOptions { // The name of the crowdfund. // This will also carry over to the governance party. string name; // The token symbol for both the crowdfund and the governance NFTs. string symbol; // The ERC721 contract of the NFT being bought. IERC721 nftContract; // How long this crowdfund has to bid on the NFT, in seconds. uint40 duration; // Maximum amount this crowdfund will pay for the NFT. // If zero, no maximum. uint96 maximumPrice; // An address that receives a portion of the final voting power // when the party transitions into governance. address payable splitRecipient; // What percentage (in bps) of the final total voting power `splitRecipient` // receives. uint16 splitBps; // If ETH is attached during deployment, it will be interpreted // as a contribution. This is who gets credit for that contribution. address initialContributor; // If there is an initial contribution, this is who they will delegate their // voting power to when the crowdfund transitions to governance. address initialDelegate; // The gatekeeper contract to use (if non-null) to restrict who can // contribute to this crowdfund. IGateKeeper gateKeeper; // The gate ID within the gateKeeper contract to use. bytes12 gateKeeperId; // Fixed governance options (i.e. cannot be changed) that the governance // `Party` will be created with if the crowdfund succeeds. FixedGovernanceOpts governanceOpts; }
This should be rearranged as follow to save gas :
struct CollectionBuyCrowdfundOptions { // The name of the crowdfund. // This will also carry over to the governance party. string name; // The token symbol for both the crowdfund and the governance NFTs. string symbol; // The ERC721 contract of the NFT being bought. IERC721 nftContract; // How long this crowdfund has to bid on the NFT, in seconds. uint40 duration; // Maximum amount this crowdfund will pay for the NFT. // If zero, no maximum. uint96 maximumPrice; // What percentage (in bps) of the final total voting power `splitRecipient` // receives. uint16 splitBps; // An address that receives a portion of the final voting power // when the party transitions into governance. address payable splitRecipient; // If ETH is attached during deployment, it will be interpreted // as a contribution. This is who gets credit for that contribution. address initialContributor; // If there is an initial contribution, this is who they will delegate their // voting power to when the crowdfund transitions to governance. address initialDelegate; // The gatekeeper contract to use (if non-null) to restrict who can // contribute to this crowdfund. IGateKeeper gateKeeper; // The gate ID within the gateKeeper contract to use. bytes12 gateKeeperId; // Fixed governance options (i.e. cannot be changed) that the governance // `Party` will be created with if the crowdfund succeeds. FixedGovernanceOpts governanceOpts; }
uints/ints
smaller than 32 bytes (256 bits) incurs overhead :When 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 as you can check here.
So use uint256
/int256
for state variables and then downcast to lower sizes where needed.
There are 6 instances of this issue:
File: contracts/crowdfund/BuyCrowdfundBase.sol 60 uint40 public expiry; 62 uint96 public maximumPrice; 64 uint96 public settledPrice; File: contracts/crowdfund/Crowdfund.sol 93 uint96 public totalContributions; 104 uint16 public splitBps; File: contracts/party/PartyGovernance.sol 199 uint16 public feeBps;
bool
for storage incurs overhead :Use uint256(1)
and uint256(2)
instead of 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 are 3 instances of this issue:
File: contracts/crowdfund/Crowdfund.sol 106 bool private _splitRecipientHasBurned; File: contracts/distribution/TokenDistributor.sol 62 bool public emergencyActionsDisabled; File: contracts/party/PartyGovernance.sol 197 bool public emergencyExecuteDisabled;
array.length
should not be looked up in every loop in a for loop :There are 9 instances of this issue:
File: contracts/crowdfund/CollectionBuyCrowdfund.sol 62 for (uint256 i; i < hosts.length; i++) File: contracts/crowdfund/Crowdfund.sol 180 for (uint256 i = 0; i < contributors.length; ++i) 300 for (uint256 i = 0; i < preciousTokens.length; ++i) File: contracts/distribution/TokenDistributor.sol 230 for (uint256 i = 0; i < infos.length; ++i) 239 for (uint256 i = 0; i < infos.length; ++i) File: contracts/party/PartyGovernance.sol 306 for (uint256 i=0; i < opts.hosts.length; ++i) File: contracts/proposals/ArbitraryCallsProposal.sol 52 for (uint256 i = 0; i < hadPreciouses.length; ++i) 61 for (uint256 i = 0; i < calls.length; ++i) 78 for (uint256 i = 0; i < hadPreciouses.length; ++i)
There is 1 instances of this issue:
File: contracts/crowdfund/CollectionBuyCrowdfund.sol 62 for (uint256 i; i < hosts.length; i++)
There are 11 instances of this issue:
File: contracts/crowdfund/CollectionBuyCrowdfund.sol 62 for (uint256 i; i < hosts.length; i++) File: contracts/crowdfund/Crowdfund.sol 180 for (uint256 i = 0; i < contributors.length; ++i) 242 for (uint256 i = 0; i < numContributions; ++i) 300 for (uint256 i = 0; i < preciousTokens.length; ++i) 348 for (uint256 i = 0; i < numContributions; ++i) File: contracts/distribution/TokenDistributor.sol 230 for (uint256 i = 0; i < infos.length; ++i) 239 for (uint256 i = 0; i < infos.length; ++i) File: contracts/party/PartyGovernance.sol 306 for (uint256 i=0; i < opts.hosts.length; ++i) File: contracts/proposals/ArbitraryCallsProposal.sol 52 for (uint256 i = 0; i < hadPreciouses.length; ++i) 61 for (uint256 i = 0; i < calls.length; ++i) 78 for (uint256 i = 0; i < hadPreciouses.length; ++i)
If a variable is not set/initialized, it is assumed to have the default value (0 for uint or int, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
There are 10 instances of this issue:
File: contracts/crowdfund/Crowdfund.sol 180 for (uint256 i = 0; i < contributors.length; ++i) 242 for (uint256 i = 0; i < numContributions; ++i) 300 for (uint256 i = 0; i < preciousTokens.length; ++i) 348 for (uint256 i = 0; i < numContributions; ++i) File: contracts/distribution/TokenDistributor.sol 230 for (uint256 i = 0; i < infos.length; ++i) 239 for (uint256 i = 0; i < infos.length; ++i) File: contracts/party/PartyGovernance.sol 306 for (uint256 i=0; i < opts.hosts.length; ++i) File: contracts/proposals/ArbitraryCallsProposal.sol 52 for (uint256 i = 0; i < hadPreciouses.length; ++i) 61 for (uint256 i = 0; i < calls.length; ++i) 78 for (uint256 i = 0; i < hadPreciouses.length; ++i)
X += Y/X -= Y
costs more gas than X = X + Y/X = X - Y
for state variables :There are 2 instances of this issue:
File: contracts/crowdfund/Crowdfund.sol 411 totalContributions += amount; File: contracts/distribution/TokenDistributor.sol 381 _storedBalances[balanceId] -= amount;