code4rena-2023-11-party-dao-m02
[M-02] PartyGovernanceNFT.mint - User can delegate another users funds to themselves and brick them from changing the delegation
Summary
유저가 아직 투표권 위임자를 설정하지 않았다면 contributeFor 함수를 통해 일부 투자금을 지원해주며 무단으로 표를 위임받을 수 있다.
Keyword
51% attack, dao, dao delegate
Vulnerability
- crowdfund/InitialETHCrowdfund.sol#L235-L249
- crowdfund/InitialETHCrowdfund.sol#L302
- party/PartyGovernanceNFT.sol#L195-L198
contributeFor 함수를 호출하면 다른 사람의 명의로 투자를 해줄 수 있다. 이 때 initialDelegate 을 설정할 수 있다. 과거에 recipient가 투표권 위임자를 설정하지 않았다면 recipient의 위임자를 initialDelegate 로 설정할 수 있다.
function contributeFor(
uint256 tokenId,
address payable recipient,
address initialDelegate,
bytes memory gateData
) external payable onlyDelegateCall returns (uint96 votingPower) {
return
@> _contribute(
recipient,
initialDelegate,
msg.value.safeCastUint256ToUint96(),
tokenId,
gateData
);
}
function _contribute(
address payable contributor,
address delegate,
uint96 amount,
uint256 tokenId,
bytes memory gateData
) private returns (uint96 votingPower) {
// Require a non-null delegate.
if (delegate == address(0)) {
revert InvalidDelegateError();
}
// Must not be blocked by gatekeeper.
IGateKeeper _gateKeeper = gateKeeper;
if (_gateKeeper != IGateKeeper(address(0))) {
if (!_gateKeeper.isAllowed(msg.sender, gateKeeperId, gateData)) {
revert NotAllowedByGateKeeperError(msg.sender, _gateKeeper, gateKeeperId, gateData);
}
}
@> votingPower = _processContribution(contributor, delegate, amount);
// OK to contribute with zero just to update delegate.
if (amount == 0) return 0;
if (tokenId == 0) {
// Mint contributor a new party card.
@> party.mint(contributor, votingPower, delegate);
} else if (disableContributingForExistingCard) {
revert ContributingForExistingCardDisabledError();
} else if (party.ownerOf(tokenId) == contributor) {
// Increase voting power of contributor's existing party card.
party.increaseVotingPower(tokenId, votingPower);
} else {
revert NotOwnerError(tokenId);
}
}party.mint 에서는 아직 위임자가 설정되지 않았을 때 delegate 파라미터를 넘기면 해당 주소를 투표권 위임자로 설정한다. 위임자가 이미 있다면 이전 위임자를 이용한다.
function mint(
address owner,
uint256 votingPower,
address delegate
) external returns (uint256 tokenId) {
...
// Use delegate from party over the one set during crowdfund.
@> address delegate_ = delegationsByVoter[owner];
@> if (delegate_ != address(0)) {
@> delegate = delegate_;
}
@> _adjustVotingPower(owner, votingPower_.safeCastUint96ToInt192(), delegate);
_safeMint(owner, tokenId);
}따라서 아직 위임자가 없는 유저들에게 contributeFor를 호출하면 표를 무단으로 위임받을 수 있고, 이를 통해 51% 공격을 하기 쉬워진다.
Impact
무단으로 표를 위임받아 51% 공격을 쉽게 할 수 있다.
Mitigation
contributeFor 함수를 삭제한다.
tags: bughunting, party dao, smart contract, solidity, 51% attack, dao, dao delegate, severity medium