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: 10/65
Findings: 2
Award: $776.77
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: TresDelinquentes
Also found by: 0xbrett8571, KupiaSec, cccz
716.7564 USDC - $716.76
SetGovernanceParameterProposal
allows critical governance parameters to be changed while proposal votes are active. This enables manipulating outcomes compared to the rules when voting began.
The _executeSetGovernanceParameter() function in SetGovernanceParameterProposal.sol executes parameter changes immediately without checking for active votes.
This is caused by:
SetGovernanceParameterProposal
executes the governance parameter changes immediately.So, as a result,
This allows retroactively altering rules, like lowering vote thresholds, to change outcomes:
// Proposal with 60% pass threshold proposal.create(threshold=6000) // Vote with 40% yes votes proposal.vote(YES, ...) // Would fail under 60% threshold // Lower threshold to 40% setParamsProposal.execute(threshold=4000) // Proposal now retroactively passes // Even though initially lacked votes for 60% threshold
The following test demonstrates the risk of changing governance parameters mid-proposal with SetGovernanceParameterProposal
.
contract SetGovernanceParameterProposalTest { Governor governor; SetGovernanceParameterProposal setParamsProposal; // Pass threshold originally 60% uint256 originalThreshold = 6000; function testChangesMidVote() public { // Create proposal with original 60% threshold Proposal proposal = governor.createProposal(...); // Vote YES with 40% of votes // Fails under original 60% threshold governor.castVote(proposal.id, YES, ...); // Create proposal to lower threshold to 40% setParamsProposal = new SetGovernanceParameterProposal( threshold: 4000 // Lower to 40% ); // Vote YES to lower threshold governor.castVote(setParamsProposal.id, YES, ...); // Pass proposal to lower threshold to 40% governor.endVote(setParamsProposal.id); // Original proposal now retroactively passes // Even though initially lacked votes under original 60% threshold governor.endVote(proposal.id); // Should fail but passes due to mid-vote rule change assert(governor.isProposalPassed(proposal.id) == true); } }
This shows how retroactively changing the pass threshold during an active vote can alter the outcome compared to the rules when voting started.
Manual review
Freeze parameters during votes
Adding a check in SetGovernanceParameterProposal
to pause voting if proposals are active would prevent this manipulation.
Check for active proposals before allowing parameter changes
Pause voting before applying new parameters
Emit events on parameter changes so clients can detect risk
Disallow parameter changes if any active proposal exists
This will prevent manipulating active proposal outcomes by changing rules mid-vote.
Governance
#0 - ydspa
2023-11-12T13:36:54Z
Insufficient proof
#1 - c4-pre-sort
2023-11-12T13:37:00Z
ydspa marked the issue as insufficient quality report
#2 - c4-judge
2023-11-19T15:56:55Z
gzeon-c4 marked the issue as duplicate of #295
#3 - c4-judge
2023-11-19T15:57:07Z
gzeon-c4 changed the severity to 2 (Med Risk)
#4 - c4-judge
2023-11-19T15:57:11Z
gzeon-c4 marked the issue as satisfactory
🌟 Selected for report: catellatech
Also found by: 0xSmartContract, 0xbrett8571, Bauchibred, K42, Myd, SAAJ, ZanyBonzy, clara, foxb868, hunter_w3b, kaveyjoe, pavankv
60.0107 USDC - $60.01
Overview
The Party Protocol enables groups to coordinate and collectively govern assets via proposals and voting. The core unit is a Party - a virtual group account that members can make proposals and vote on. Parties are formed via crowdfunds where contributors get voting power.
The protocol architecture consists of the following key components:
Component | Description | Key Contracts |
---|---|---|
Party | Holds assets, controls governance, token contract for NFTs | Party, PartyGovernance, PartyGovernanceNFT |
Proposals | Mechanism for members to create and vote on on-chain actions | ProposalExecutionEngine |
Crowdfund | Manages pooling funds to form a Party | Crowdfund contracts |
Token Distribution | Distributes assets to Party members | TokenDistributor |
Gatekeepers | Restricts access to crowdfunds | AllowListGateKeeper, TokenGateKeeper |
Renderers | Generates metadata for NFTs | Renderer contracts |
The core components are Parties and Proposals. Crowdfund contracts allow the creation of new Parties.
The other components like Gatekeepers, Renderers, and TokenDistribution provide supporting capabilities.
The architecture and flow of the Party Protocol:
sequenceDiagram participant User participant Frontend participant Party participant Crowdfund participant Globals participant OpenSea User->>Frontend: Initiate Crowdfund alt Buy Specific NFT Frontend->>Crowdfund: Create BuyCrowdfund Crowdfund->>Globals: Get implementation Globals-->>Crowdfund: BuyCrowdfund impl Crowdfund->>Party: Deploy Party Party->>Globals: Get params Globals-->>Party: Params else Raise Funds Frontend->>Crowdfund: Create InitialETHCrowdfund Crowdfund->>Globals: Get implementation Globals-->>Crowdfund: InitialETHCrowdfund impl Crowdfund->>Party: Deploy Party Party->>Globals: Get params Globals-->>Party: Params end alt Successful Crowdfund->>User: Transfer NFT User->>OpenSea: List NFT for sale else Failed Crowdfund->>User: Refund ETH end User->>Frontend: Propose Action Frontend->>Party: Create proposal Party->>User: Notify to vote User->>Party: Vote on proposal alt Passes Party->>OpenSea: Execute proposal OpenSea-->>Party: Confirm execution else Fails Party->>User: Notify proposal failed end
This shows:
External contracts like Zora are referenced for executing proposals involving those protocols.
Component | Description | Key Contracts |
---|---|---|
Party | Holds assets, controls governance, token contract for NFTs | Party, PartyGovernance, PartyGovernanceNFT |
Proposals | Mechanism for members to create and vote on on-chain actions | ProposalExecutionEngine |
Crowdfund | Manages pooling funds to form a Party | Crowdfund contracts |
Token Distribution | Distributes assets to Party members | TokenDistributor |
Gatekeepers | Restricts access to crowdfunds | AllowListGateKeeper, TokenGateKeeper |
Renderers | Generates metadata for NFTs | Renderer contracts |
The core components are Parties and Proposals. Crowdfund contracts allow the creation of new Parties.
The other components like Gatekeepers, Renderers, and TokenDistribution provide supporting capabilities.
Security Analysis
Issue | Description | Mitigation |
---|---|---|
Incorrect voting power snapshots | Potential double counting of votes if snapshot logic has bug | Thorough auditing of voting power accounting code |
Ruggable assets | Unprotected access to Party's assets like ETH or ERC20s | Audit asset transfer logic and access controls |
Malicious proposals | Bad proposals like stealing NFTs or rugging assets | Review proposal types thoroughly, give hosts veto power |
Flash loan attacks | Quick borrowing of funds to manipulate governance | Use minimum voting and execution delays |
Smart Contracts
The main smart contracts are:
Contract | Purpose |
---|---|
Party | Holds assets, controls governance, token contract for membership NFTs |
PartyGovernance | Logic for voting, proposals, distributions, ragequit |
PartyGovernanceNFT | ERC721 token logic for membership NFTs |
ProposalExecutionEngine | Executes proposal logic |
Crowdfund | Manages pooling funds to form a Party |
Key Mechanisms
The Voting Power mechanism is a core part of the protocol. It allows tracking the voting rights of each member over time. Key aspects:
The complex parts are handling delegation and transfers:
_adjustVotingPower
- updates voter and delegate voting power on change._rebalanceDelegates
- redistributes voting power when delegation changes._transferVotingPower
- transfers voting power when NFT transferred._getVotingPowerSnapshotAt
- retrieves voting power at any point in time.Thorough testing of all edge cases is recommended due to complexity.
The Proposals mechanism allows members to create and vote on on-chain actions.
execute
logic abstracted into ProposalExecutionEngine.The Distribution mechanism lets parties divide up assets proportionally:
Rage Quit gives members an "exit" option:
This provides liquidity without relying on secondary markets.
Here is a diagram explaining the key mechanisms in the Party Protocol:
graph TD subgraph PartyContract Party --> Governance Party --> Token end subgraph GovernanceContract VotingPower Proposals Distributions RageQuit end subgraph TokenContract Minting Burning Transfers end style Party fill:#ddd,color:#000 style GovernanceContract fill:#bbb,color:#000 style TokenContract fill:#bbb,color:#000
The Party contract contains the governance logic and tokens.
GovernanceContract handles:
TokenContract implements membership NFTs:
The governance and token capabilities combine to form the core mechanisms that allow parties to collectively govern assets.
Trust Model
The protocol gives significant authority to these privileged roles:
This necessitates authorities and hosts being highly trusted entities.
The Authorities and Hosts have significant control:
Authorities pose the biggest risk if compromised. They should be carefully permissioned contracts rather than trusted individuals. Regular audits are essential.
For hosts, there is safety in numbers - requiring multiple hosts to veto creates decentralization.
Diagram of the Party Protocol's trust model with explanation:
graph TD subgraph Trusted Roles Authorities["Authorities"]-->PartyGovernance Hosts["Hosts"] --> PartyGovernance end subgraph Party Contracts PartyGovernanceNFT --> PartyGovernance PartyGovernance --> Party end subgraph External Contracts PropEngineImpl["ProposalEngine<br>Implementation"]-->Party end style Authorities fill:#08f,color:#fff style Hosts fill:#08f,color:#fff style Party fill:#ddd,color:#000 style PartyGovernance fill:#bbb,color:#000 style PartyGovernanceNFT fill:#bbb,color:#000
Authorities have full administrative privileges over the Party:
Authorities are intended to be highly audited and trusted contracts.
Hosts can:
The Party contract delegates execution logic to the ProposalEngineImplementation. This is a trusted dependency that requires security audits.
The Party also relies on the PartyGovernance and PartyGovernanceNFT contracts for core functionality.
In summary, the Party fully trusts Authorities and Hosts roles as well as the ProposalEngineImplementation and governance contracts. These trusted components should be carefully vetted before deployment.
All the admin functions and modifiers in the Party Protocol contracts
PartyGovernanceNFT.sol
Admin Functions:
Modifiers:
PartyGovernance.sol
Admin Functions:
Modifiers:
ProposalExecutionEngine.sol
Admin Functions:
Modifiers:
InitialETHCrowdfund.sol
Admin Functions:
Modifiers:
The Party Protocol changelog:
Add ERC1271 Signing
Explanation: Allow Parties to implement ERC1271 interface to enable collective signing of off-chain messages. Useful for web2 app integrations.
Scenario: Party members vote to sign a message allowing them access to a collaborative Docs app.
Change Governance Settings
Explanation: Store governance params like voting duration in separate contract. Allows changing via proposals.
Scenario: Party members vote to reduce voting period from 1 week to 3 days.
Skip Veto on Full Approval
Explanation: If all Hosts vote yes, skip the veto waiting period. Speeds up execution.
Scenario: A non-contentious proposal gets quick unanimous Host approval, goes straight to execution.
Decrease Voting Power
Explanation: Authorities can now reduce voting power of specific NFTs. Enables fluid adjustments.
Scenario: An external contract burns NFTs if members are inactive, reducing their voting power.
Decrease Total Voting Power
Explanation: Authorities can reduce total voting power supply after initializing. Needed since they can now burn NFTs.
Scenario: Authorities burn NFTs, then reduce total supply to match.
ERC1167 Proxies
Explanation: Upgrade to ERC1167 for compatibility with tools like Etherscan.
Scenario: Party instances get auto-verified on Etherscan after upgrading.
Crowdfund Authorities
Explanation: Crowdfunds can now grant additional authorities to newly formed Parties.
Scenario: Crowdfund makes itself an authority to mint NFTs.
Move Storage
Explanation: Shift storage out of Party contract to reduce size.
Scenario: Party contract now fits within size limits for mainnet.
graph LR subgraph V1 PartyV1[Party<br>V1] PVNV1[PGNFT<br>V1] PEEV1[PEE<br>V1] end PartyV1 --custom proxy--> PartyV2 PVNV1 --custom proxy--> PVNV2 PEEV1 --custom proxy--> PEEV2 subgraph V2 PartyV2[Party<br>V2] PVNV2[PGNFT<br>V2] PEEV2[PEE<br>V2] end PartyV2 -- ERC1167 proxy --> PartyV3 PVNV2 -- ERC1167 proxy --> PVNV3 PEEV2 -- ERC1167 proxy --> PEEV3 subgraph V3 PartyV3[Party<br>V3] PVNV3[PGNFT<br>V3] PEEV3[PEE<br>V3] PEEV3 -- new impl --> PEEV4[PEE<br>V4] PVNV3 -- ERC1271 --> Signing PartyV3 -- Change Settings --> Settings end subgraph V4 PartyV4[Party<br>V4] PVNV4[PGNFT<br>V4] end
V2
Scenario: Auto-verify Party on Etherscan
V3
Scenario: Party signs message to access Docs app
Scenario: Reduce voting period via proposal
V4
Scenario: Add special proposal types
Code Quality
Some areas that could be improved:
OffChainSignatureValidator
.Some positives in the code:
To improve:
Conclusion
The Party Protocol demonstrates well-designed modular architecture and has taken care to limit risk surface area. A few areas of concern exist around voting power tracking complexity and privilege levels of roles like authorities.
38 hours
#0 - c4-pre-sort
2023-11-13T10:41:06Z
ydspa marked the issue as sufficient quality report
#1 - c4-judge
2023-11-20T18:36:57Z
gzeon-c4 marked the issue as grade-b