Party DAO - dharma09's results

Protocol for group coordination.

General Information

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

PartyDAO

Findings Distribution

Researcher Performance

Rank: 53/65

Findings: 1

Award: $23.81

Gas:
grade-b

🌟 Selected for report: 0

šŸš€ Solo Findings: 0

Findings Information

Awards

23.8054 USDC - $23.81

Labels

bug
G (Gas Optimization)
grade-b
sufficient quality report
G-06

External Links

GAS OPTIMIZATIONS

Findings

[G-01] Use global variable rather than caching global variable

File: contracts/crowdfund/InitialETHCrowdfund.sol
204: function batchContribute(
        BatchContributeArgs calldata args
    ) external payable onlyDelegateCall returns (uint96[] memory votingPowers) {
        uint256 numContributions = args.tokenIds.length;
        votingPowers = new uint96[](numContributions);

        uint256 ethAvailable = msg.value; //@audit Do Not Cache global variable
        for (uint256 i; i < numContributions; ++i) {
            ethAvailable -= args.values[i];

            votingPowers[i] = _contribute(
                payable(msg.sender),
                args.delegate,
                args.values[i],
                args.tokenIds[i],
                args.gateDatas[i]
            );
        }

        // Refund any unused ETH.
        if (ethAvailable > 0) payable(msg.sender).transfer(ethAvailable);
    }
File: contracts/crowdfund/InitialETHCrowdfund.sol
204: function batchContribute(
        BatchContributeArgs calldata args
    ) external payable onlyDelegateCall returns (uint96[] memory votingPowers) {
        uint256 numContributions = args.tokenIds.length;
        votingPowers = new uint96[](numContributions);

-        uint256 ethAvailable = msg.value; 
+        uint256 ethAvailable;
        for (uint256 i; i < numContributions; ++i) {
-            ethAvailable -= args.values[i];
+            ethAvailable = msg.value - args.values[i]
            votingPowers[i] = _contribute(
                payable(msg.sender),
                args.delegate,
                args.values[i],
                args.tokenIds[i],
                args.gateDatas[i]
            );
        }

        // Refund any unused ETH.
-        if (ethAvailable > 0) payable(msg.sender).transfer(ethAvailable);
+        if (msg.value > 0) payable(msg.sender).transfer(msg.value);
    }

[G-02] Use cache value instead of new read

File: contracts/party/PartyGovernanceNFT.sol
208: function increaseVotingPower(uint256 tokenId, uint96 votingPower) external {
        _assertAuthority();
        uint96 mintedVotingPower_ = mintedVotingPower;
        uint96 totalVotingPower = _getSharedProposalStorage().governanceValues.totalVotingPower;

        // Cap voting power to remaining unminted voting power supply. Allow
        // minting past total voting power if minting party cards for initial
        // crowdfund when there is no total voting power.
        if (totalVotingPower != 0 && totalVotingPower - mintedVotingPower_ < votingPower) {
            unchecked {
                votingPower = totalVotingPower - mintedVotingPower_;
            }
        }

        // Update state.
        mintedVotingPower += votingPower; //@audit Use cache value
        uint256 newIntrinsicVotingPower = votingPowerByTokenId[tokenId] + votingPower;
        votingPowerByTokenId[tokenId] = newIntrinsicVotingPower;

        emit PartyCardIntrinsicVotingPowerSet(tokenId, newIntrinsicVotingPower);

        _adjustVotingPower(ownerOf(tokenId), votingPower.safeCastUint96ToInt192(), address(0));
    }
File: contracts/party/PartyGovernanceNFT.sol
208: function increaseVotingPower(uint256 tokenId, uint96 votingPower) external {
        _assertAuthority();
        uint96 mintedVotingPower_ = mintedVotingPower;
        uint96 totalVotingPower = _getSharedProposalStorage().governanceValues.totalVotingPower;

        // Cap voting power to remaining unminted voting power supply. Allow
        // minting past total voting power if minting party cards for initial
        // crowdfund when there is no total voting power.
        if (totalVotingPower != 0 && totalVotingPower - mintedVotingPower_ < votingPower) {
            unchecked {
                votingPower = totalVotingPower - mintedVotingPower_;
            }
        }

        // Update state.
-        mintedVotingPower += votingPower;
+        mintedVotingPower =  mintedVotingPower + votingPower;
        uint256 newIntrinsicVotingPower = votingPowerByTokenId[tokenId] + votingPower;
        votingPowerByTokenId[tokenId] = newIntrinsicVotingPower;

        emit PartyCardIntrinsicVotingPowerSet(tokenId, newIntrinsicVotingPower);

        _adjustVotingPower(ownerOf(tokenId), votingPower.safeCastUint96ToInt192(), address(0));
    }

[G-03] Optimize gas usage by avoiding variable declarations inside loops

The variablesĀ tokenId,Ā owner,Ā token, andĀ amount are all declared inside the loop. This means that a new instance of each variable will be created for each iteration of the loop. SavesĀ 200-300 GasĀ per iteration

File: contracts/party/PartyGovernanceNFT.sol
273: uint256 tokenId = tokenIds[i]; //@audit
274: address owner = ownerOf(tokenId);
File: contracts/party/PartyGovernanceNFT.sol
409: IERC20 token = withdrawTokens[i]; //@audit
410: uint256 amount = withdrawAmounts[i];

[G-04] optimize event for better gas optimization

File: contracts/party/PartyGovernanceNFT.sol
318: function setRageQuit(uint40 newRageQuitTimestamp) external {
        _assertHost();
        // Prevent disabling ragequit after initialization.
        if (newRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY) {
            revert CannotDisableRageQuitAfterInitializationError();
        }

        uint40 oldRageQuitTimestamp = rageQuitTimestamp;

        // Prevent setting timestamp if it is permanently enabled/disabled.
        if (
            oldRageQuitTimestamp == ENABLE_RAGEQUIT_PERMANENTLY ||
            oldRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY
        ) {
            revert FixedRageQuitTimestampError(oldRageQuitTimestamp);
        }

        emit RageQuitSet(oldRageQuitTimestamp, rageQuitTimestamp = newRageQuitTimestamp); //@audit rageQuitTimestamp
    } 
File: contracts/party/PartyGovernanceNFT.sol
318: function setRageQuit(uint40 newRageQuitTimestamp) external {
        _assertHost();
        // Prevent disabling ragequit after initialization.
        if (newRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY) {
            revert CannotDisableRageQuitAfterInitializationError();
        }

        uint40 oldRageQuitTimestamp = rageQuitTimestamp;
        rageQuitTimestamp = newRageQuitTimestamp

        // Prevent setting timestamp if it is permanently enabled/disabled.
        if (
            oldRageQuitTimestamp == ENABLE_RAGEQUIT_PERMANENTLY ||
            oldRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY
        ) {
            revert FixedRageQuitTimestampError(oldRageQuitTimestamp);
        }
-
-        emit RageQuitSet(oldRageQuitTimestamp, rageQuitTimestamp = newRageQuitTimestamp); 
+        emit RageQuitSet(oldRageQuitTimestamp, newRageQuitTimestamp);   
 } 

[G-05] Use Gas optimized version of IERC2981 and IERC1271 library instead of openzeppelin library

Solady’sĀ IERC2981Ā implementation is more gas efficient than OZ's version. I recommend to switch to that library instead

File: contracts/party/PartyGovernanceNFT.sol
6: import "openzeppelin/contracts/interfaces/IERC2981.sol"; //@audit Use solady library's
File: contracts/proposals/ProposalExecutionEngine.sol
7: import { IERC1271 } from "openzeppelin/contracts/interfaces/IERC1271.sol"; //@audit Use solady library's
File: contracts/signature-validators/OffChainSignatureValidator.sol
4: import { IERC1271 } from "openzeppelin/contracts/interfaces/IERC1271.sol"; //@audit Use solady library's

[G-06] Do not cache state variable if used once

If function use variable only once then it doesn’t make sense to cache that variable in memory it just waste of gas.Recommeded avoid such allocation.

File: contracts/crowdfund/ETHCrowdfundBase.sol
261: address payable fundingSplitRecipient_ = fundingSplitRecipient; //@audit
        uint16 fundingSplitBps_ = fundingSplitBps;
        if (fundingSplitRecipient_ != address(0) && fundingSplitBps_ > 0) {
            // Removes funding split from contribution amount in a way that
            // avoids rounding errors for very small contributions <1e4 wei.
            amount = (amount * (1e4 - fundingSplitBps_)) / 1e4;
        }
File: contracts/crowdfund/ETHCrowdfundBase.sol
-261: address payable fundingSplitRecipient_ = fundingSplitRecipient; //@audit
        uint16 fundingSplitBps_ = fundingSplitBps;
-        if (fundingSplitRecipient_ != address(0) && fundingSplitBps_ > 0) {
+        if (fundingSplitRecipient != address(0) && fundingSplitBps_ > 0) {
            // Removes funding split from contribution amount in a way that
            // avoids rounding errors for very small contributions <1e4 wei.
            amount = (amount * (1e4 - fundingSplitBps_)) / 1e4;
        }
File: contracts/crowdfund/ETHCrowdfundBase.sol
322: address payable fundingSplitRecipient_ = fundingSplitRecipient; //@audit
        uint16 fundingSplitBps_ = fundingSplitBps;
        if (fundingSplitRecipient_ != address(0) && fundingSplitBps_ > 0) {
            totalContributions_ -= (totalContributions_ * fundingSplitBps_) / 1e4;
        }
File: contracts/crowdfund/ETHCrowdfundBase.sol
-322: address payable fundingSplitRecipient_ = fundingSplitRecipient; //@audit
        uint16 fundingSplitBps_ = fundingSplitBps;
-        if (fundingSplitRecipient_ != address(0) && fundingSplitBps_ > 0) {
+        if (fundingSplitRecipient != address(0) && fundingSplitBps_ > 0) {
            totalContributions_ -= (totalContributions_ * fundingSplitBps_) / 1e4;
        }

[G-07] Optimize theĀ sendFndingSplit() function for better gas efficiency

File: contracts/crowdfund/ETHCrowdfundBase.sol
339: function sendFundingSplit() external returns (uint96 splitAmount) {
        // Check that the crowdfund is finalized.
        CrowdfundLifecycle lc = getCrowdfundLifecycle();
        if (lc != CrowdfundLifecycle.Finalized) revert WrongLifecycleError(lc);

        if (fundingSplitPaid) revert FundingSplitAlreadyPaidError(); //@audit Revert first

        address payable fundingSplitRecipient_ = fundingSplitRecipient;
        uint16 fundingSplitBps_ = fundingSplitBps;
        if (fundingSplitRecipient_ == address(0) || fundingSplitBps_ == 0) {
            revert FundingSplitNotConfiguredError();
        }

        fundingSplitPaid = true;

        // Transfer funding split to recipient.
        splitAmount = (totalContributions * fundingSplitBps_) / 1e4;
        payable(fundingSplitRecipient_).transferEth(splitAmount);

        emit FundingSplitSent(fundingSplitRecipient_, splitAmount);
    }
File: contracts/crowdfund/ETHCrowdfundBase.sol
339: function sendFundingSplit() external returns (uint96 splitAmount) {
        // Check that the crowdfund is finalized.
+      if (fundingSplitPaid) revert FundingSplitAlreadyPaidError();
        CrowdfundLifecycle lc = getCrowdfundLifecycle();
        if (lc != CrowdfundLifecycle.Finalized) revert WrongLifecycleError(lc);

-       if (fundingSplitPaid) revert FundingSplitAlreadyPaidError(); 
        address payable fundingSplitRecipient_ = fundingSplitRecipient;
        uint16 fundingSplitBps_ = fundingSplitBps;
        if (fundingSplitRecipient_ == address(0) || fundingSplitBps_ == 0) {
            revert FundingSplitNotConfiguredError();
        }

        fundingSplitPaid = true;

        // Transfer funding split to recipient.
        splitAmount = (totalContributions * fundingSplitBps_) / 1e4;
        payable(fundingSplitRecipient_).transferEth(splitAmount);

        emit FundingSplitSent(fundingSplitRecipient_, splitAmount);
    }

[G-08] Low levelĀ callĀ can be optimized with assembly

When using low-level calls, theĀ returnDataĀ is copied to memory even if the variable is not utilized. The proper way to handle this is through a low level assembly call. For example:

(bool success,) = payable(receiver).call{gas: gas, value: value}("");

can be optimized to:

bool success;
assembly {
    success := call(gas, receiver, value, 0, 0, 0, 0)
}
File: contracts/crowdfund/ETHCrowdfundBase.sol
379: (bool success, bytes memory res) = targetAddress.call{ value: amountEth }(targetCallData); //@audit
        if (!success) {
            res.rawRevert();
        }0
File: contracts/crowdfund/InitialETHCrowdfund.sol
358: (bool s, bytes memory r) = address(this).call( //@audit
                abi.encodeCall(this.refund, (tokenIds[i]))
            ); 
File: ontracts/party/PartyGovernance.sol
846: (bool success, bytes memory res) = targetAddress.call{ value: amountEth }(targetCallData); //@audit

#0 - c4-pre-sort

2023-11-13T06:56:04Z

ydspa marked the issue as sufficient quality report

#1 - c4-judge

2023-11-19T18:26:38Z

gzeon-c4 marked the issue as grade-b

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