Submitted by hyh
burn() allows for previously recorded delegate to set himself to be contributors delegate even if another one was already chosen.
This can be quite material as owner choice for the whole voting power is being reset this way to favor the old delegate.
\_burn() can be invoked by anyone on the behalf of any contributor:
https://github.com/PartyDAO/party-contracts-c4/blob/3896577b8f0fa16cba129dc2867aba786b730c1b/contracts/crowdfund/Crowdfund.sol#L167-L171
It mints the governance NFT for the contributor whenever he has voting power:
https://github.com/PartyDAO/party-contracts-c4/blob/3896577b8f0fa16cba129dc2867aba786b730c1b/contracts/crowdfund/Crowdfund.sol#L471-L485
Now mint() calls \_adjustVotingPower() with a new delegate, redirecting all the intristic power, not just one for that id, ignoring the delegation the owner might already have:
https://github.com/PartyDAO/party-contracts-c4/blob/3896577b8f0fa16cba129dc2867aba786b730c1b/contracts/party/PartyGovernanceNFT.sol#L120-L133
I.e. Bob the contributor can take part in the crowdfunding with contribute() with small 0.01 ETH stake, stating Mike as the delegate of his choice with contribute(Mike, ...):
https://github.com/PartyDAO/party-contracts-c4/blob/3896577b8f0fa16cba129dc2867aba786b730c1b/contracts/crowdfund/Crowdfund.sol#L189-L208
Then crowdfund was a success, party was created, and Melany, who also participated, per off-chain arrangement has transferred to Bob a tokenId with big voting power (say it is 100 ETH and the majority of voting power):
https://github.com/PartyDAO/party-contracts-c4/blob/3896577b8f0fa16cba129dc2867aba786b730c1b/contracts/party/PartyGovernanceNFT.sol#L146-L155
https://github.com/PartyDAO/party-contracts-c4/blob/3896577b8f0fa16cba129dc2867aba786b730c1b/contracts/party/PartyGovernance.sol#L879-L887
Bob dont care about his early small contribution and focuses on managing the one that Melany transferred instead as he simply doesnt need the voting power from the initial 0.01 ETH contribution anymore.
The actual delegate for Bob at the moment is Linda, while his business with Mike is over. So Bob sets her address there, calling delegateVotingPower(Linda):
https://github.com/PartyDAO/party-contracts-c4/blob/3896577b8f0fa16cba129dc2867aba786b730c1b/contracts/party/PartyGovernance.sol#L448-L454
Now, Mike can unilaterally delegate to himself the whole voting power with burn(Bob) as mint() just resets the delegation with the previously recorded value with _adjustVotingPower(owner, votingPower.safeCastUint256ToInt192(), delegate).
The issue is that mint() always assumes that it is the first operation for the owner, which might not always be the case.
Consider not changing the delegate on mint if one is set already:
https://github.com/PartyDAO/party-contracts-c4/blob/3896577b8f0fa16cba129dc2867aba786b730c1b/contracts/party/PartyGovernanceNFT.sol#L120-L133
More complicated version might be the one with tracking the most recent request via contribute()/delegateVotingPower() calls timestamps. Here we assume that the delegateVotingPower() holds more information as in the majority of practical cases it occurs after initial contribute() and it is a direct voluntary call from the owner.
merklejerk (PartyDAO) confirmed, but disagreed with severity and commented:
HardlyDifficult (judge) decreased severity to Medium and commented:
0xble (PartyDAO) resolved:
