xanoutas/solidity-security-findings huggingface dataset:
Malicious user can prevent a Crowdfund from reaching the maxTotalContributions
Audit: 2023-10-party-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/ETHCrowdfundBase.sol#L229-L243
https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/ETHCrowdfundBase.sol#L255-L258


# Vulnerability details

## Impact
Malicious user can deliberately craft a contribution that will cause the next contributor's amount to be less than minContributions. This would make the totalContributions to never reach the maxTotalContributions, which would be unfair to a Party.

## Proof of Concept
When contributing in InitialETHCrowdfund, if totalContributions reaches the maxTotalContributions, the Crowdfund gets finalized, and the totalContributions will be transferred to the Party

`ETHCrowdfundBase#_processContribution`, which is called by `InitialETHCrowdfund#_contribute`, [makes sure](https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/ETHCrowdfundBase.sol#L229-L243) that the `amount` being contributed does not make totalContributions>maxTotalContributions. It does this by reducing the `amount` to `maxTotalContributions-totalContributions`, and refunding the excess back to the caller.

At the same time, there is a minContribution that [must be contributed](https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/ETHCrowdfundBase.sol#L255-L258), so if the recalculated `amount` is less than minContribution, the call will revert. This means that the Crowdfund will never attain maxTotalContributions.

Consider the following scenario:
- minTotalContributions=20, maxTotalContributions=30, minContribution=5, maxContribution=10
- People contribute, totalContributions=20
- Alice wants to contribute 10 eth to make the totalContributions=maxTotalContributions=30, so that the Crowdfund gets finalized
- Bob frontruns Alice contribution with a 6 eth contribution, 
    - totalContributions=26 eth
- Alice 10 eth contribution gets reduced to maxTotalContributions-totalContributions=30-26=4 eth. Since 4 is less than the minContribution of 5 eth, Alice's transaction gets reverted.
- Nobody can make the totalContributions=maxTotalContributions, which is not so fair for the Party, as it is kinda like a shortage, and was done deliberately by malicious Bob

## Tools Used
Manual Review

## Recommended Mitigation Steps
If `maxTotalContributions-newTotalContribution<minContributions`, call should revert. A user's contribution should either finalize a crowdfund or allow for another user to contribute.
From the example above, Bob's transaction should revert, which would leave him with two options:
1. deposit <=5 eth so that another user can contribute >=5 eth(which would finalize the crowdfund).
2. contribute 10 eth and finalize the crowdfund


## Assessed type

Other
Proposal execution reentrancy
Audit: 2023-10-party-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-party/blob/main/contracts/proposals/ProposalExecutionEngine.sol#L146


# Vulnerability details

## Impact
When executeProposal() is called, it directly executes the proposal logic via _execute(). This proposal logic has full access to call back into the execution engine through delegatecall.

For example, a malicious proposal could call executeProposal() again recursively before the first call finishes.

This would allow it to bypass the proposal locking mechanism and execute additional operations.

It could also directly manipulate variables like nextProgressDataHash before the first execution completes.

A reentrant call could even clear the in-progress ID via cancelProposal() to rollback the vote.

```solidity
    function cancelProposal(uint256 proposalId) external onlyDelegateCall {
```
https://github.com/code-423n4/2023-10-party/blob/main/contracts/proposals/ProposalExecutionEngine.sol#L207C1-L208C1


The execution engine relies on proposal code being non-reentrant but doesn't enforce it.

It should add checks like:


1. Requiring tx.origin matches party before proposal execution
2. Checking for reentrancy via checks on msg.sender vs this during execution

Without these, proposals have unfettered ability to recursively manipulate state.

A proposed "upgrade" contract could even hijack control flow via reentry bugs.

lack of reentrancy protection allows proposals uncontrolled ability to directly manipulate critical state variables and bypass intended execution flow.


## Tools Used

Manual Review



## Assessed type

Reentrancy
Governance execute Function Vulnerability in Smart Contract
Audit: 2023-10-party-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L706


# Vulnerability details

## Impact

 Lack of validation for progressData and extraData in the execute function of the governance smart contract.

```
function execute(
    uint256 proposalId,
    Proposal memory proposal,
    IERC721[] memory preciousTokens,
    uint256[] memory preciousTokenIds,
    bytes calldata progressData,
    bytes calldata extraData
) external payable { ... }

```
Problem Description: 

The execute function, designed for executing proposals that have passed governance, lacks sufficient validation for the progressData and extraData parameters. This oversight allows any user to input custom data into these parameters, potentially deviating from the intended governance process.

Specific Concerns:

Front-Running and Manipulation: Malicious actors can exploit this vulnerability to front-run legitimate transactions, inserting their own progressData and extraData, thereby influencing the outcome of proposal execution.
Data Integrity: Without proper validation, the integrity of the data used in the proposal execution process is compromised, leading to potential misexecution of governance actions.


## Proof of Concept
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernance.sol#L706

## Tools Used

manual

## Recommended Mitigation Steps

Implement rigorous validation checks for progressData and extraData within the execute function. These validations should ensure that the data provided aligns with the intended execution path of the governance proposal.


## Assessed type

Context
After canceling a proposal all other attempts to execute a proposal would revert
Audit: 2023-10-party-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/proposals/ProposalExecutionEngine.sol#L146-L204


# Vulnerability details


## Impact

If any proposal gets cancelled all other other proposals wouldn't be executable.

## Proof of Concept

The [`cancelProposal()`](https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/proposals/ProposalExecutionEngine.sol#L207) is used to be forcibly cancel an incomplete proposal, there is the intention of this being last resort, so whenever possible, proposals should be allowed to complete their entire lifecycle, below is it's implementation.

```solidity
function cancelProposal(uint256 proposalId) external onlyDelegateCall {
  // Must be a valid proposal ID.
  if (proposalId == 0) {
    revert ZeroProposalIdError();
  }
  Storage storage stor = _getStorage();
  {
    // Must be the current InProgress proposal.
    uint256 currentInProgressProposalId = stor.currentInProgressProposalId;
    if (currentInProgressProposalId != proposalId) {
      revert ProposalNotInProgressError(proposalId);
    }
  }
  //@audit
  stor.currentInProgressProposalId = 0;
  stor.nextProgressDataHash = 0;
}
```

Issue is that, whenever a proposal gets cancelled, the `current InProgress proposal ID and next progress data` gets cleared, or in other words reset to 0, this entirely breaks the idea in `executeProposal()` since each proposals to be executed are somewhat chained.

Take a look at [ `executeProposal()`](https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/proposals/ProposalExecutionEngine.sol#L146-L204), most especially the initial proposal ID check and assignment:

```solidity
if (params.proposalId == 0) {
    revert ZeroProposalIdError();
}
...
//@audit-info this would be true after  `cancelProposal()` has been executed
if (currentInProgressProposalId == 0) {
    // No proposal is currently in progress.
    stor.currentInProgressProposalId = params.proposalId;
} else if (currentInProgressProposalId != params.proposalId) {
    // Only one proposal can be in progress at a time.
    revert ProposalExecutionBlockedError(params.proposalId, currentInProgressProposalId);
}
```

`if (currentInProgressProposalId == 0)` would be true after `cancelProposal()` has been executed, note that after that this line: `stor.currentInProgressProposalId = params.proposalId` updates the `proposalId` and it's no longer 0.

Below is the subsequent actions taken in `executeProposal()` with regards to `nextProgressDataHash`

```solidity
            bytes32 nextProgressDataHash = stor.nextProgressDataHash;
            //@audit-info This would be true after `cancelProposal()`
            if (nextProgressDataHash == 0) {
                // Expecting no progress data.
                // This is the state if there is no current `InProgress` proposal.
                assert(currentInProgressProposalId
possibility of Reentrancy attack in `rageQuit()` function
Audit: 2023-10-party-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernanceNFT.sol#L356-L357
https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernanceNFT.sol#L369


# Vulnerability details


## Bug Description
The `rageQuit` function is designed to allow users to withdraw a fair share of fungible tokens from the party by burning their NFT. This function is callable by both users and `authorities` (a list of partyDao protocol contracts). However, the current implementation, while preventing Reentrancy attacks for users, leaves a potential vulnerability open for authorities. The mechanism to prevent Reentrancy involves setting the `rageQuitTimestamp` to `DISABLE_RAGEQUIT_PERMANENTLY`, effectively disabling it. This works for users, but if an authority contract calls this function, it can apply a Reentrancy attack.

## Impact
The [rageQuit](https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernanceNFT.sol#L344-L448) function has a mechanism to prevent Reentrancy attacks by setting the rageQuitTimestamp to be equal to `DISABLE_RAGEQUIT_PERMANENTLY`. However, this prevents only users from applying Reentrancy attacks. If an authority contract makes a call to this function, the caller can bypass the check` (if (!isAuthority_))` and apply a Reentrancy attack in this [line](https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernanceNFT.sol#L357-L366). The protocol mentions that they have plans to add more authority contracts in the future, increasing the likelihood of such a case occurring.

This is considered a medium-level vulnerability because, at the moment, no authority contracts make calls to this function. However, due to the protocol's future plans, this vulnerability becomes more likely.

mentioned in [changeLog](https://github.com/code-423n4/2023-10-party#changelog)
"To enable parties to immediately have the ability to interact with the AddPartyCardsAuthority as well `as other authorities coming in the future` "

## Proof of Concept
Analyzing the rageQuit function reveals that it follows a mechanism to prevent Reentrancy attacks by setting the `rageQuitTimeStamp` to disable it:

```solidity
 function rageQuit(
        uint256[] calldata tokenIds,
        IERC20[] calldata withdrawTokens,
        uint256[] calldata minWithdrawAmounts,
        address receiver
    ) external {
        if (tokenIds.length == 0) revert NothingToBurnError();

        // Check if called by an authority.
        bool isAuthority_ = isAuthority[msg.sender];

        // Check if ragequit is allowed.
        uint40 currentRageQuitTimestamp = rageQuitTimestamp;
        if (!isAuthority_) {
            if (currentRageQuitTimestamp != ENABLE_RAGEQUIT_PERMANENTLY) {
                if (
                    currentRageQuitTimesta
Smart Contract Code - Use of transfer Method
Audit: 2023-10-party-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/InitialETHCrowdfund.sol#L224


# Vulnerability details

## Impact

The current implementation of the smart contract uses the transfer method for refunding unused Ether. This is problematic due to the 2300 gas stipend limit imposed by transfer.

```
// Refund any unused ETH.
if (ethAvailable > 0) payable(msg.sender).transfer(ethAvailable);

```
he transfer method in Solidity automatically forwards a fixed gas stipend of 2300 gas. This stipend is often insufficient for executing more complex fallback functions, potentially leading to a failure of the transaction if the receiving contract's fallback function requires more gas.

Recommended Solution: Replace transfer with call. The call method in Solidity forwards all available gas by default, unless otherwise specified. This reduces the risk of failing due to insufficient gas when interacting with contracts having more complex fallback functions.

## Proof of Concept
https://github.com/code-423n4/2023-10-party/blob/main/contracts/crowdfund/InitialETHCrowdfund.sol#L224C1-L224C74
## Tools Used

## Recommended Mitigation Steps

```
// Refund any unused ETH.
if (ethAvailable > 0) (bool sent, ) = payable(msg.sender).call{value: ethAvailable}("");
require(sent, "Failed to send Ether");
```
 Implementing this change will increase the robustness of the contract's ETH transfer mechanism, reducing the risk of transaction failures due to gas limitations.


## Assessed type

Token-Transfer
Excess ETH Contribution is lost during the `PartyGovernanceNFT::mint()` function
Audit: 2023-10-party-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernanceNFT.sol#L179-L181


# Vulnerability details

## Impact
The `PartyGovernanceNFT::mint()` function seems to disregard any excess ETH which brings the `mintedVotingPower` above the `totalVotingPower`. In this case it adjusts the votingPower that the user can have to fit within the remaining limit. This adjustment leads to a situation where the excess ETH contributed, does not grant any voting power.
User might contribute expecting some voting power equivalent but receives way less than expected or none.

## Proof of Concept
```javascript
if (totalVotingPower != 0 && totalVotingPower - mintedVotingPower_ < votingPower_) {
            unchecked {
@>                votingPower_ = totalVotingPower - mintedVotingPower_;
            }
        }

        // Update state.
        unchecked {
            tokenId = ++tokenCount;
        }
        mintedVotingPower += votingPower_;
@>        votingPowerByTokenId[tokenId] = votingPower_;
```

The `votingPowerByTokenId` can only be increased by an amount for which `totalVotingPower - mintedVotingPower_ < votingPower_`. Meaning if `votingPower` derived from user's contribution does not check this condition, the ETH sentis not translated into `votingPower`.

### POC:


```javascript
function test_contribute_Max_Amount_mintNewCard() public {
        InitialETHCrowdfund crowdfund = _createCrowdfund(
            CreateCrowdfundArgs({
                initialContribution: 0,
                initialContributor: payable(address(0)),
                initialDelegate: address(0),
                minContributions: 0,
                maxContributions: type(uint96).max,
                disableContributingForExistingCard: false,
                minTotalContributions: 3 ether,
                maxTotalContributions: 5 ether,
                duration: 10 days,
                exchangeRateBps: 1e4,
                fundingSplitBps: 0,
                fundingSplitRecipient: payable(address(0)),
                gateKeeper: IGateKeeper(address(0)),
                gateKeeperId: bytes12(0)
            })
        );
        Party party = crowdfund.party();

        address member = _randomAddress();
        vm.deal(member, 20 ether);

        // Contribute
        vm.prank(member);
        // vm.expectEmit(true, false, false, true);
        // emit Contributed(member, member, 1 ether, member);
        crowdfund.contribute{ value: 5 ether }(member, "");

        uint256 tokenId = 1;
        console.log(crowdfund.totalContributions());
        console.log(party.getGovernanceValues().totalVotingPower);
        console.log(address(member).balance);
        console.log(party.votingPowerByTokenId(tokenId));
        console.log(address(crowdfund).balance);
```
As of now, `totalVotingPower` has increased by 5 as member has contributed by the maxTotalContributions. If the party increase the `maxTotalContributio
function : `decreaseVotingPower`  incorrectly updates the delegate voting power. it decreases the owner voting power only.
Audit: 2023-10-party-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernanceNFT.sol#L236-L242
https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernance.sol#L944
https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernance.sol#L972


# Vulnerability details

## Impact
Delegate voting power would be increased/unaffected even if the owner voting power is reduced.

## Proof of Concept

An authorized person would call the function [decreaseVotingPower](https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernanceNFT.sol#L236-L242) to decrease the voting power of a NFT token.

This function would call `_adjustVotingPower` and adjust the voting power of token holder as well as the delegate.

https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernance.sol#L944-L1018

where the voting power is adjusted to the token holder and the voting power of the delegate of old and new are balanced by calling the function `_rebalanceDelegates`.

when we look at the source function i.e, `decreaseVotingPower`, the `votingPower` is sent as negative value after typecasting.

`_adjustVotingPower` correctly reduces the voting power of the token holder.

https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernance.sol#L953-L961

```solidity
        VotingPowerSnapshot memory newSnap = VotingPowerSnapshot({
            timestamp: uint40(block.timestamp),
            delegatedVotingPower: oldSnap.delegatedVotingPower,
            intrinsicVotingPower: (oldSnap.intrinsicVotingPower.safeCastUint96ToInt192() +
                votingPower).safeCastInt192ToUint96(), --------------------------------------->>>> reduced here.
            isDelegated: delegate != voter
        });
        _insertVotingPowerSnapshot(voter, newSnap);
        delegationsByVoter[voter] = delegate;
```
But when we look at the `_rebalanceDelegates` function, the [condition](https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernance.sol#L996) will be executed if the voter already delegated the voting power to the delegate.
https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/party/PartyGovernance.sol#L996-L1017

```solidity
        if (oldDelegate != voter && oldDelegate != newDelegate) { ---------->>> this will not executed
            // Remove past voting power from old delegate.
            VotingPowerSnapshot memory oldDelegateSnap = _getLastVotingPowerSnapshotForVoter(
                oldDelegate
            );
            VotingPowerSnapshot memory updatedOldDelegateSnap = VotingPowerSnapshot({
                timestamp: uint40(blo
Missing lower boundary check for totalVotingPower in PartyGovernanceNFT.decreaseTotalVotingPower function
Audit: 2023-10-party-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernanceNFT.sol#L255-L258


# Vulnerability details

## Impact
In PartyGovernanceNFT.decreaseTotalVotingPower function, the totalVotingPower is being decreased by the given arbitrary `votingPower` value. Even though the function is gaurded by the authorities, it must make sure that the `totalVotingPower` must not decrease less than the `mintedVotingPower`.
Since everywhere the contract ensured the mintedVotingPower should not increase more than totalVotingPower.

## Recommended Mitigation Steps
Place a condition to check require(totalVotingPower - votingPower >= mintedVotingPower) in decreaseTotalVotingPower function.


## Assessed type

Context
Some arbitrary proposal calls will fail because executeProposal() in ProposalExecutionEngine is not payable
Audit: 2023-10-party-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/proposals/ProposalExecutionEngine.sol#L148


# Vulnerability details

The `executeProposal()` function in `ProposalExecutionEngine.sol` is used to execute different types of proposal call. One of this type is `ArbitraryCallsProposal` and this type should be able to send ether from the Party's balance or the forwarded ETH attached to this call - msg.value. However, the problem is that the `executeProposal()` function is not marked as payable and all calls that are sending ether and using msg.value will fail because of that. 

Note: Even though this function is delegatecalled from `PartyGovernance.sol::execute()` which is payable the msg.value is still preserved when delegatecall is used and the function called needs to be payable too. 

## Impact

All proposal calls that are using attached ether to it will fail and Party's wont be able to execute these types of proposals which can be important. 


## Proof of Concept

https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/proposals/ProposalExecutionEngine.sol#L146-L148

```solidity
146:   function executeProposal(
147:     ExecuteProposalParams memory params
148:   ) external onlyDelegateCall returns (bytes memory nextProgressData) { 

```
As you can see the `executeProposal()` function is not payable however later it then calls `_executeArbitraryCalls()`:

https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/proposals/ArbitraryCallsProposal.sol#L72-L74

```solidity
72:  // If we're not allowing arbitrary calls to spend the Party's ETH, only
73:  // allow forwarded ETH attached to the call to be spent.
74:  uint256 ethAvailable = allowArbCallsToSpendPartyEth ? address(this).balance : msg.value;

```
As you can see and as the comment suggests the call should be able to use the forwarded ether attached to the call and `ethAvailable` is then used in `_executeSingleArbitraryCall()` but because the function is not payable the call will revert.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Make the `executeProposal()` function payable


## Assessed type

Payable