Closed c4-submissions closed 10 months ago
ydspa marked the issue as duplicate of #480
ydspa marked the issue as insufficient quality report
gzeon-c4 marked the issue as unsatisfactory: Invalid
gzeon-c4 marked the issue as not a duplicate
gzeon-c4 changed the severity to QA (Quality Assurance)
It is the party authority responsibility to not intentionally break critical invariant, but a safe guard can be nice @0xble
Lines of code
https://github.com/code-423n4/2023-10-party/blob/main/contracts/party/PartyGovernanceNFT.sol#L255
Vulnerability details
Bug Description
The recent changes to the protocol introduced the functionality for an authority to reduce the
totalVotingPower
of the party using the functiondecreaseTotalVotingPower()
. The function allows an authority to decrease thetotalVotingPower
by an arbitrary number, up to the currenttotalVotingPower
. However, if the authority decreases thetotalVotingPower
to 0, it results in multiple issues.Impact
1. Users Cannot Create New Proposals
Calls to the
propose()
function will revert every time, due to thepropose()
function calling theaccept()
function. Theaccept()
function includes a call to_areVotesPassing()
which will revert due to a division by 0 in the following line.This not only results in a loss of functionality but also grants authority the power to stop the creation of new proposals intentionally, leading to potential governance freezing.
2.
OffChainValidator
Functionality Denial-of-Service (DOS)If
totalVotingPower
is set to 0 andthresholdBps
is not 0 in theOffChainValidator
, calls toisValidSignature()
will revert. This is due to a division by zero in the following lines:This vulnerability results in a denial-of-service (DOS) of the
ERC1271
functionality.3. No Ragequitting will be possible
If
totalVotingPower
gets set to 0, users will not be able to ragequit anymore. This is due to a call togetVotingPowerShareOf()
inrageQuit()
which then calculates:This calculation will lead to a division by 0, reverting everytime. This restriction denies users the ability to withdraw their funds and disassociate from the party.
4. Incorrect NFT SVGs will be rendered
The
PartyNFTRender
contract generates erroneous SVGs for NFTs whentotalVotingPower
is 0. The VotingPowerPercentage becomes "--" due to thegenerateVotingPowerPercentage()
function returning "--" in cases wheretotalVotingPower
is 0. This inaccuracy in information is then incorporated into the NFT's SVG, providing users with misleading data.5.
tokenURI()
will return incorrect metadata for NFTsIf the
totalVotingPower
get set to 0, the description in the NFTs metadata will state "This item represents membership in %partyName. Exact voting power will be determined when the crowdfund ends. Head to %generateExternalURL() to view the Party's latest activity." which would incorrectly indicates that the party has not yet started. This happens due to the functiongenerateDescription()
calling to the functionhasPartyStarted()
to determine if it should add a description for before or after the party start.hasPartyStarted()
will return false iftotalVotingPower
is 0 leading to the incorrect description.Every NFT's where the requirement
renderingMethod == RenderingMethod.FixedCrowdfund || (renderingMethod == RenderingMethod.ENUM_OFFSET && getCrowdfundType() == CrowdfundType.Fixed)
does not hold will return an incorrect name in its metadata. The name in the metadata will be "Party Membership" due to the functiongenerateName()
using the functionhasPartyStarted()
to determine if the Voting power can already be calculated. Otherwise it just returns "Party Membership" as the name.Additionally no NFT's metadata will contain any attributes due to the function
hasPartyStarted()
being used to determine if attributes should be added intokenURI
.6. Voting power of NFTs can arbitrarily be increased by Authority
When the
totalVotingPower
is set to 0, any authority can useincreaseVotingPower()
to increase the voting power of a single NFT as much as they want. This is due to the capping of themintedVotingPower_
to thetotalVotingPower
only being in place in the function iftotalVotingPower
is not 0, as one can see below.7. No distribution will be possible
If the
totalVotingPower
gets set to 0, users will not be able to recover their funds using a distribution. This is due to thedistribute()
function checking thetotalVotingPower
, and if thetotalVotingPower
is 0 assumes the governance has not yet started. You can see this from this snippet:Proof of Concept
In the following one can find multiple POCs, for the more complex of the described issues.
Governance
This POC shows the issues where users can propose no new proposals, are not able to ragequit, no distributes can be generated and the authority can increase NFT's voting power arbitrarily:
The POC can be run by adding it to the
GovernanceNFT.t.sol
file and running it usingforge test -vvvv --match-test "testTotalVotingPowerBecomesZero"
OffChainValidator
This POC shows that the
OffChainValidator
will stop working whentotalVotingPower
is 0.The POC can be run by adding it to the
OffChainSignatureValidator.t.sol
file and running it usingforge test -vvvv --match-test "testDOSifTotalVotingPowerIsZero"
Tools Used
Manual Review
Recommended Mitigation Steps
The issue can be mitigated by checking if the
totalVotingPower
would be reduced to 0 by a call todecreaseTotalVotingPower()
, and if that is the case reverting. This can be done by adapting the function like this:Assessed type
Math