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: 54/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
struct
Can be packed more preciselyETHPartyOptions
struct can be re-order to save 1 storage slotPreviously 6 Slots used excluding arrays
Now 5 Slots
Gas saved 2.1k
struct ETHPartyOptions { // Name of the party. string name; // @audit string // Symbol of the party. string symbol; // The ID of the customization preset to use for the party card. uint256 customizationPresetId; // Options to initialize party governance with. Crowdfund.FixedGovernanceOpts governanceOpts; // Options to initialize party proposal engine with. ProposalStorage.ProposalEngineOpts proposalEngineOpts; + uint40 rageQuitTimestamp; // The tokens that are considered precious by the party.These are // protected assets and are subject to extra restrictions in proposals // vs other assets. IERC721[] preciousTokens; // The IDs associated with each token in `preciousTokens`. uint256[] preciousTokenIds; // The timestamp until which ragequit is enabled. - uint40 rageQuitTimestamp; // @audit G:: Re-arange to save gas // Initial authorities to set on the party address[] authorities; }
https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/InitialETHCrowdfund.sol#L60
>
, !=
could used- if (initialContribution > 0) { + if (initialContribution != 0) {
- if (amount > 0) { // @audit + if (amount != 0) { // Get contributor to refund. address payable contributor = payable(party.ownerOf(tokenId));
- fundingSplitBps_ > 0 + fundingSplitBps_ != 0
https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/InitialETHCrowdfund.sol#L141 https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/InitialETHCrowdfund.sol#L331
https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/ETHCrowdfundBase.sol#L324
IGateKeeper
Assembly could be used
- if (_gateKeeper != IGateKeeper(address(0))) { + if (_gateKeeper != address(0)) {
https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/InitialETHCrowdfund.sol#L289
value + 1
, unchecked{ ++value }
could be usedfunction _createParty( ETHPartyOptions memory opts, MetadataProvider customMetadataProvider, bytes memory customMetadata ) private returns (Party) { - uint256 authoritiesLength = opts.authorities.length + 1; + uint256 authoritiesLength = unchecked{ ++opts.authorities.length};
https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/InitialETHCrowdfund.sol#L377
} else { // Entry is older. This is our best guess for now. - low = mid + 1; // @audit G :: + low = unchecked{ ++mid }; }
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L440
Getters for public state variables are automatically generated by the solidity compiler so there is no need to code them manually as this increases deployment cost.
mapping(uint256 => uint256) public votingPowerByTokenId; function getDistributionShareOf(uint256 tokenId) external view returns (uint256) { // @audit already defauylt getter there return votingPowerByTokenId[tokenId]; }
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernanceNFT.sol#L146-L148
type(uint96).max
, VETO_VALUE
should useduint96 private constant VETO_VALUE = type(uint96).max; - if (pv.votes == type(uint96).max) { + if (pv.votes == VETO_VALUE) { return ProposalStatus.Defeated; }
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L1083
if (govOpts.feeBps > 1e4) { // @audit G :: Magic Numbuer revert InvalidBpsError(govOpts.feeBps); } if (govOpts.passThresholdBps > 1e4) { // @audit G :: revert InvalidBpsError(govOpts.passThresholdBps); }
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L277 https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L280
i++
should be unchecked{++i}
in loops. [This was missed in Bot report]PartyGovernanceNFT.sol ( #L102, #L272, #L378, #L391, #L408 ):
102: for (uint256 i; i < authorities.length; ++i) { 272: for (uint256 i; i < tokenIds.length; ++i) { 378: for (uint256 i; i < withdrawTokens.length; ++i) { 391: for (uint256 j; j < tokenIds.length; ++j) { 408: for (uint256 i; i < withdrawTokens.length; ++i) {
PartyGovernance.sol ( #L305 )
305: for (uint256 i = 0; i < govOpts.hosts.length; ++i) {
InitialETHCrowdfund.sol ( #L260 )
260: for (uint256 i; i < args.recipients.length; ++i) {
- uint256 low = 0; + uint256 low; while (low < high) { uint256 mid = (low + high) / 2;
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L432
function _getProposalFlags(ProposalStateValues memory pv) private pure returns (uint256) { - uint256 flags = 0; + uint256 flags;
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L1054
_getSharedProposalStorage()
should be cached in memory rather than calling it againif (msg.sender != address(this)) { // Must not require a vote to create a distribution, otherwise // distributions can only be created through a distribution // proposal. - if (_getSharedProposalStorage().opts.distributionsRequireVote) { revert DistributionsRequireVoteError(); } // Must be an active member. VotingPowerSnapshot memory snap = _getLastVotingPowerSnapshotForVoter(msg.sender); if (snap.intrinsicVotingPower == 0 && snap.delegatedVotingPower == 0) { revert NotAuthorized(); } } // Prevent creating a distribution if the party has not started. - if (_getSharedProposalStorage().governanceValues.totalVotingPower == 0) { revert PartyNotStartedError(); }
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L500 https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L510
type(uint256).max
should stored in a constant state variable
emit BatchMetadataUpdate(0, type(uint256).max);
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L688
emit BatchMetadataUpdate(0, type(uint256).max);
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L833
return getVotingPowerAt(voter, timestamp, type(uint256).max);
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L359
return high == 0 ? type(uint256).max : high - 1;
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L445
emit BatchMetadataUpdate(0, type(uint256).max);
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L520
emit BatchMetadataUpdate(0, type(uint256).max);
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L581
emit BatchMetadataUpdate(0, type(uint256).max);
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L655
emit BatchMetadataUpdate(0, type(uint256).max);
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L897
if (hintIndex != type(uint256).max) {
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L926
2
times storage write to info.values
could reduced to 1
ProposalState storage info = _proposalStateByProposalId[proposalId]; ProposalStateValues memory values = info.values; ........ ........ values.votes += votingPower; // @audit votes and voating power mixed here check pls if (isHost[msg.sender]) { ++values.numHostsAccepted; } - info.values = values; // @audit G :: 2 time storage write emit ProposalAccepted(proposalId, msg.sender, votingPower); // Update the proposal status if it has reached the pass threshold. if ( values.passedTime == 0 && _areVotesPassing( values.votes, values.totalVotingPower, _getSharedProposalStorage().governanceValues.passThresholdBps ) ) { - info.values.passedTime = uint40(block.timestamp); // @audit here again + values.passedTime = uint40(block.timestamp); + info.values = values; emit ProposalPassed(proposalId); // Notify third-party platforms that the governance NFT metadata has // updated for all tokens. emit BatchMetadataUpdate(0, type(uint256).max); } return values.votes;
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L639 https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L651
info.values
could directly used instead of caching
it to memory
function veto(uint256 proposalId) external { _assertHost(); // Setting `votes` to -1 indicates a veto. ProposalState storage info = _proposalStateByProposalId[proposalId]; - ProposalStateValues memory values = info.values; { - ProposalStatus status = _getProposalStatus(values); // @audit G:: info.values + ProposalStatus status = _getProposalStatus(info.values); // Proposal must be in one of the following states. if ( status != ProposalStatus.Voting && status != ProposalStatus.Passed && status != ProposalStatus.Ready ) { revert BadProposalStatusError(status); } } // -1 indicates veto. info.values.votes = VETO_VALUE; emit ProposalVetoed(proposalId, msg.sender); // Notify third-party platforms that the governance NFT metadata has // updated for all tokens. emit BatchMetadataUpdate(0, type(uint256).max); // @audit G:: }
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L672
Following test shows Gas difference
library NoNamedReturnArithmetic { function sum(uint256 num1, uint256 num2) internal pure returns(uint256){ return num1 + num2; } } contract NoNamedReturn { using NoNamedReturnArithmetic for uint256; uint256 public stateVar; function add2State(uint256 num) public { stateVar = stateVar.sum(num); } }
test for test/NoNamedReturn.t.sol:NamedReturnTest [PASS] test_Increment() (gas: 27639)
library NamedReturnArithmetic { function sum(uint256 num1, uint256 num2) internal pure returns(uint256 theSum){ theSum = num1 + num2; } } contract NamedReturn { using NamedReturnArithmetic for uint256; uint256 public stateVar; function add2State(uint256 num) public { stateVar = stateVar.sum(num); } }
test for test/NamedReturn.t.sol:NamedReturnTest [PASS] test_Increment() (gas: 27613)
function _isUnanimousVotes( uint96 totalVotes, uint96 totalVotingPower - ) private pure returns (bool) { + ) private pure returns (bool val) { // @audit uint256 acceptanceRatio = (totalVotes * 1e4) / totalVotingPower; - return acceptanceRatio >= 0.9999e4; + val = acceptanceRatio >= 0.9999e4; }
function _hostsAccepted( uint8 snapshotNumHosts, uint8 numHostsAccepted - ) private pure returns (bool) { // @audit - return snapshotNumHosts > 0 && snapshotNumHosts == numHostsAccepted; + ) private pure returns (bool val) { + val = snapshotNumHosts > 0 && snapshotNumHosts == numHostsAccepted; }
function _areVotesPassing( uint96 voteCount, uint96 totalVotingPower, uint16 passThresholdBps - ) private pure returns (bool) { // @audit - return (uint256(voteCount) * 1e4) / uint256(totalVotingPower) >= uint256(passThresholdBps); + ) private pure returns (bool) { // @audit + return (uint256(voteCount) * 1e4) / uint256(totalVotingPower) >= uint256(passThresholdBps); }
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L1114 https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L1125 https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L1133
#0 - c4-pre-sort
2023-11-13T07:47:21Z
ydspa marked the issue as sufficient quality report
#1 - c4-judge
2023-11-19T18:28:40Z
gzeon-c4 marked the issue as grade-b