PartyGovernanceNFT.sol#increaseTotalVotingPower - Malicious actor can front-run increaseTotalVotingPower() and rageQuit() by taking more funds from the Party and leaving less for the others
Audit: 2023-10-party-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The protocol offers enhanced functionality and flexibility to trusted actors (hosts) via the use of trusted smart contracts called authorities.

`PartyGovernanceNFT#increaseTotalVotingPower()` is one such function which is exclusively available to authorities. As the name implies, it is used to increase the total voting power of the party.

An authority which leverages that function is `AddPartyCardsAuthority`. It is an authority which atomically distributes new party cards and updates the total voting power as needed.

A malicious actor can front-run `AddPartyCardsAuthority` which calls `increaseTotalVotingPower()` and `rageQuit()` by taking more funds out of the Party than the rest of the users would be able to.

The reason is that `increaseTotalVotingPower()` dilutes the voting power of each user. Thus, when a user front-runs a call to `increaseTotalVotingPower()` they would be able to rage quit before their voting power is being diluted and would benefit from this fact by taking out more value from their Party tokens in comparison with the rest of the users.

## Proof of Concept

The `increaseTotalVotingPower()` function is pretty straightforward - it simply adds `votingPower` to the `totalVotingPower` of the party:

```js
function increaseTotalVotingPower(uint96 votingPower) external {
    _assertAuthority();
    _getSharedProposalStorage().governanceValues.totalVotingPower += votingPower;
}
```

Let's explore `rageQuit()`, which if enabled, allows users to burn a Party card and withdraw ETH and/or ERC20 tokens proportionally to the card's voting power:

```js
withdrawAmounts[i] += (balance * getVotingPowerShareOf(tokenIds[j])) / 1e18;
```

As we can see in `getVotingPowerShareOf(tokenId)`, the voting power share of a card is calculated by dividing it to the `totalVotingPower` of the Party, denominated in `1e18`:

```js
function getVotingPowerShareOf(uint256 tokenId) public view returns (uint256) {
    uint256 totalVotingPower = _getSharedProposalStorage().governanceValues.totalVotingPower;
    return
        totalVotingPower == 0 ? 0 : (votingPowerByTokenId[tokenId] * 1e18) / totalVotingPower;
}
```

Given the above, let's replace the voting power share formula in the calculation of withdraw amounts in `rageQuit()`:

$$
Withdraw\ Amount = \frac{Balance \times Token\ Voting\ Power \times \cancel{1e18}}{Total\ Voting\ Power \times \cancel{1e18}}
$$
$$
Withdraw\ Amount = \frac{Balance \times Token\ Voting\ Power}{Total\ Voting\ Power}
$$

where

$Balance$ - the ETH/ERC20 balance of the Party

$Token\ Voting\ Power$ - the voting power of the token being burned during `rageQuit()`

$Total\ Voting\ Power$ - the total voting power of the Party

Let's take the following example.

**Prerequisites:**
- We have a party that only has ETH balance
- `exchangeRateB