Platform: Code4rena
Start Date: 31/10/2023
Pot Size: $60,500 USDC
Total HM: 9
Participants: 65
Period: 10 days
Judge: gzeon
Total Solo HM: 2
Id: 301
League: ETH
Rank: 52/65
Findings: 1
Award: $23.81
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xVolcano
Also found by: 0x11singh99, 0xAnah, 0xhacksmithh, 0xhex, 0xta, DavidGiladi, K42, Topmark, arjun16, dharma09, hunter_w3b, lsaudit, pavankv, tabriz, tala7985, ybansal2403
23.8054 USDC - $23.81
Number | Issue | Instances |
---|---|---|
[G-01] | Used already cached value of state variable rather than re-reading from storage | 1 |
[G-02] | Don't cache state variable if it is used only once | 6 |
[G-03] | Don't emit state variable | 1 |
[G-04] | Do not increase opts.authorities.length with 1 | 1 |
[G-05] | Use constant instead of type(x).max to save gas | 12 |
[G-06] | Check Eth /amount for zero before transfer | 2 |
[G-07] | Remove unused error /event | 5 |
[G-08] | Can Make The Variable Outside The Loop To Save Gas | 9 |
state variable
rather than re-reading from storage1 Instance in 1 File
File : contracts/crowdfund/ETHCrowdfundBase.sol 227: uint96 newTotalContributions = totalContributions + amount; 228: uint96 maxTotalContributions_ = maxTotalContributions; 229: if (newTotalContributions >= maxTotalContributions_) { 230: totalContributions = maxTotalContributions_; 231: 232: // Finalize the crowdfund. 233: // This occurs before refunding excess contribution to act as a 234: // reentrancy guard. 235: _finalize(maxTotalContributions_); 236: 237: // Refund excess contribution. 238: uint96 refundAmount = newTotalContributions - maxTotalContributions;
File : contracts/crowdfund/ETHCrowdfundBase.sol uint96 newTotalContributions = totalContributions + amount; uint96 maxTotalContributions_ = maxTotalContributions; if (newTotalContributions >= maxTotalContributions_) { totalContributions = maxTotalContributions_; // Finalize the crowdfund. // This occurs before refunding excess contribution to act as a // reentrancy guard. _finalize(maxTotalContributions_); // Refund excess contribution. - uint96 refundAmount = newTotalContributions - maxTotalContributions; + uint96 refundAmount = newTotalContributions - maxTotalContributions_;
state
variable if it is used only once6 Instances in 4 Files
<details> <summary>see instances</summary>File : contracts/party/PartyGovernance.sol 1033: if (n != 0) { 1034: VotingPowerSnapshot memory lastSnap = voterSnaps[n - 1]; //@audit Don't cache `voterSnaps[n - 1]` 1035: if (lastSnap.timestamp == snap.timestamp) { 1036: voterSnaps[n - 1] = snap; 1037: return; 1038: } 1039: }
File : contracts/proposals/ProposalExecutionEngine.sol 215: uint256 currentInProgressProposalId = stor.currentInProgressProposalId; //@audit don't cache `stor.currentInProgressProposalId` 216: if (currentInProgressProposalId != proposalId) { 217: revert ProposalNotInProgressError(proposalId); 218: } 219: } 220: // Clear the current InProgress proposal ID and next progress data. 221: stor.currentInProgressProposalId = 0; 222: stor.nextProgressDataHash = 0; 223: }
File : contracts/proposals/ProposalExecutionEngine.sol 336: uint256 slot = _STORAGE_SLOT; //@audit don't cache `_STORAGE_SLOT` 337: assembly { 338: stor.slot := slot 339: }
File : contracts/proposals/ProposalStorage.sol 45: IProposalExecutionEngine oldImpl = stor.engineImpl;//@audit don't cache `stor.engineImpl` 46: stor.engineImpl = impl; 47: (bool s, bytes memory r) = address(impl).delegatecall( 48: abi.encodeCall(IProposalExecutionEngine.initialize, (address(oldImpl), initData)) 49: ); 50: if (!s) { 51: r.rawRevert(); 52: } 53: }
File : contracts/proposals/ProposalStorage.sol 60: uint256 s = SHARED_STORAGE_SLOT;//@audit don't cache `SHARED_STORAGE_SLOT` 61: assembly { 62: stor.slot := s 63: }
</details>File : contracts/proposals/SetSignatureValidatorProposal.sol 52: uint256 slot = _SET_SIGNATURE_VALIDATOR_PROPOSAL_STORAGE_SLOT;//@audit don't cache `_SET_SIGNATURE_VALIDATOR_PROPOSAL_STORAGE_SLOT` 53: assembly { 54: stor.slot := slot 55: }
emit
state
variable1 Instance in 1 File
File : contracts/party/PartyGovernanceNFT.sol 333: } 334: 335: emit RageQuitSet(oldRageQuitTimestamp, rageQuitTimestamp = newRageQuitTimestamp); 336: }
File : contracts/party/PartyGovernanceNFT.sol } + rageQuitTimestamp = newRageQuitTimestamp; + - emit RageQuitSet(oldRageQuitTimestamp, rageQuitTimestamp = newRageQuitTimestamp); + emit RageQuitSet(oldRageQuitTimestamp, newRageQuitTimestamp); }
opts.authorities.length
with 1If you can avoid unnecessary operations within a loop, it can potentially save gas. So directly cache opts.authorities.length
in authoritiesLength
and you created a new array authorities
that is 1 element longer than opts.authorities.length
.
1 Instance in 1 File
File : contracts/crowdfund/InitialETHCrowdfund.sol 377: uint256 authoritiesLength = opts.authorities.length + 1; 378: address[] memory authorities = new address[](authoritiesLength); 379: for (uint i = 0; i < authoritiesLength - 1; ++i) { 380: authorities[i] = opts.authorities[i]; 381: } 382: authorities[authoritiesLength - 1] = address(this);
File : contracts/crowdfund/InitialETHCrowdfund.sol - uint256 authoritiesLength = opts.authorities.length + 1; - address[] memory authorities = new address[](authoritiesLength); - for (uint i = 0; i < authoritiesLength - 1; ++i) { + uint256 authoritiesLength = opts.authorities.length; + address[] memory authorities = new address[](authoritiesLength + 1); + for (uint i = 0; i < authoritiesLength; ++i) { authorities[i] = opts.authorities[i]; } - authorities[authoritiesLength - 1] = address(this); + authorities[authoritiesLength] = address(this);
type(x).max
to save gas12 Instances in 2 Files
<details> <summary>see instances</summary>File : contracts/party/PartyGovernance.sol 187: if (govOpts.hosts.length > type(uint8).max) {
File : contracts/party/PartyGovernance.sol + uint8 constant MAX_TYPE = type(uint8).max; - if (govOpts.hosts.length > type(uint8).max) { + if (govOpts.hosts.length > MAX_TYPE) {
File : contracts/party/PartyGovernance.sol 359: return getVotingPowerAt(voter, timestamp, type(uint256).max); 445: return high == 0 ? type(uint256).max : high - 1; 520: emit BatchMetadataUpdate(0, type(uint256).max); 581: emit BatchMetadataUpdate(0, type(uint256).max); 655: emit BatchMetadataUpdate(0, type(uint256).max); 688: emit BatchMetadataUpdate(0, type(uint256).max); 833: emit BatchMetadataUpdate(0, type(uint256).max); 897: emit BatchMetadataUpdate(0, type(uint256).max); 926: if (hintIndex != type(uint256).max) {
359, 445, 520, 581, 655,688, 833, 897, 926
File : contracts/party/PartyGovernance.sol abstract contract PartyGovernance is ... + uint256 private constant MAX_UINT256 = type(uint256).max; ... - return getVotingPowerAt(voter, timestamp, type(uint256).max); + return getVotingPowerAt(voter, timestamp, MAX_UINT256); - return high == 0 ? type(uint256).max : high - 1; + return high == 0 ? MAX_UINT256 : high - 1; - emit BatchMetadataUpdate(0, type(uint256).max); + emit BatchMetadataUpdate(0, MAX_UINT256); - emit BatchMetadataUpdate(0, type(uint256).max); + emit BatchMetadataUpdate(0, MAX_UINT256); - emit BatchMetadataUpdate(0, type(uint256).max); + emit BatchMetadataUpdate(0, MAX_UINT256); - emit BatchMetadataUpdate(0, type(uint256).max); + emit BatchMetadataUpdate(0, MAX_UINT256); - emit BatchMetadataUpdate(0, type(uint256).max); + emit BatchMetadataUpdate(0, MAX_UINT256); - emit BatchMetadataUpdate(0, type(uint256).max); + emit BatchMetadataUpdate(0, MAX_UINT256); - if (hintIndex != type(uint256).max) { + if (hintIndex != MAX_UINT256) {
File : contracts/party/PartyGovernance.sol 187: uint96 private constant VETO_VALUE = type(uint96).max; 1083: if (pv.votes == type(uint96).max) {
File : contracts/party/PartyGovernance.sol - if (pv.votes == type(uint96).max) { + if (pv.votes == VETO_VALUE) {
File : contracts/proposals/ProposalExecutionEngine.sol 185: stor.nextProgressDataHash = bytes32(type(uint256).max);
</details>File : contracts/proposals/ProposalExecutionEngine.sol + uint256 constant MAX_TYPE = 2**256 - 1; - stor.nextProgressDataHash = bytes32(type(uint256).max); + stor.nextProgressDataHash = bytes32(MAX_TYPE);
Eth
/amount
for zero
before transfer2 Instances in 1 File
File : contracts/crowdfund/ETHCrowdfundBase.sol 333: payable(address(party)).transferEth(totalContributions_);//@audit check `totalContributions_` 356: payable(fundingSplitRecipient_).transferEth(splitAmount);//@audit check `splitAmount`
error
/event
5 Instances in 1 File
<details> <summary>see instances</summary></details>File : contracts/crowdfund/ETHCrowdfundBase.sol 51: error NotAllowedByGateKeeperError( 52: address contributor, 53: IGateKeeper gateKeeper, 54: bytes12 gateKeeperId, 55: bytes gateData 56: ); 60: error NotOwnerError(uint256 tokenId); 62: error InvalidDelegateError(); 69: error ContributingForExistingCardDisabledError(); 73: error InvalidMessageValue();
When you declare a variable inside a loop, Solidity creates a new instance of the variable for each iteration of the loop.
9 Instances in 3 Files
<details> <summary>see instances</summary>File : contracts/crowdfund/InitialETHCrowdfund.sol 357: for (uint256 i; i < numRefunds; ++i) { 358: (bool s, bytes memory r) = address(this).call(
File : contracts/crowdfund/InitialETHCrowdfund.sol + bool s; + bytes memory r; for (uint256 i; i < numRefunds; ++i) { - (bool s, bytes memory r) = address(this).call( + (s, r) = address(this).call(
File : contracts/party/PartyGovernance.sol 431: uint256 high = snaps.length; 432: uint256 low = 0; 433: while (low < high) { 434: uint256 mid = (low + high) / 2;
File : contracts/party/PartyGovernance.sol uint256 high = snaps.length; uint256 low = 0; + uint256 mid; while (low < high) { - uint256 mid = (low + high) / 2; + mid = (low + high) / 2;
File : contracts/party/PartyGovernanceNFT.sol 272: for (uint256 i; i < tokenIds.length; ++i) { 273: uint256 tokenId = tokenIds[i]; 274: address owner = ownerOf(tokenId);
File : contracts/party/PartyGovernanceNFT.sol + uint256 tokenId; + address owner; for (uint256 i; i < tokenIds.length; ++i) { - uint256 tokenId = tokenIds[i]; - address owner = ownerOf(tokenId); + tokenId = tokenIds[i]; + owner = ownerOf(tokenId);
File : contracts/party/PartyGovernanceNFT.sol 407: uint16 feeBps_ = feeBps; 408: for (uint256 i; i < withdrawTokens.length; ++i) { 409: IERC20 token = withdrawTokens[i]; 410: uint256 amount = withdrawAmounts[i]; 411: 412: // Take fee from amount. 413: uint256 fee = (amount * feeBps_) / 1e4; 414: 415: if (fee > 0) { 416: amount -= fee; 417: 418: // Transfer fee to fee recipient. 419: if (address(token) == ETH_ADDRESS) { 420: payable(feeRecipient).transferEth(fee); 421: } else { 422: token.compatTransfer(feeRecipient, fee); 423: } 424: } 425: 426: if (amount > 0) { 427: uint256 minAmount = minWithdrawAmounts[i];
</details>File : contracts/party/PartyGovernanceNFT.sol uint16 feeBps_ = feeBps; + IERC20 token; + uint256 amount; + uint256 fee; + uint256 minAmount; for (uint256 i; i < withdrawTokens.length; ++i) { - IERC20 token = withdrawTokens[i]; - uint256 amount = withdrawAmounts[i]; + token = withdrawTokens[i]; + amount = withdrawAmounts[i]; // Take fee from amount. uint256 fee = (amount * feeBps_) / 1e4; if (fee > 0) { amount -= fee; // Transfer fee to fee recipient. if (address(token) == ETH_ADDRESS) { payable(feeRecipient).transferEth(fee); } else { token.compatTransfer(feeRecipient, fee); } } if (amount > 0) { uint256 minAmount = minWithdrawAmounts[i];
#0 - c4-pre-sort
2023-11-13T06:56:02Z
ydspa marked the issue as sufficient quality report
#1 - c4-judge
2023-11-19T18:27:24Z
gzeon-c4 marked the issue as grade-b