Nouns DAO contest - Ch_301's results

A DAO-driven NFT project on Ethereum.

General Information

Platform: Code4rena

Start Date: 22/08/2022

Pot Size: $50,000 USDC

Total HM: 4

Participants: 160

Period: 5 days

Judge: gzeon

Total Solo HM: 2

Id: 155

League: ETH

Nouns DAO

Findings Distribution

Researcher Performance

Rank: 16/160

Findings: 3

Award: $1,094.57

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: TomJ

Also found by: 0xDjango, 0xSmartContract, Aymen0909, Ch_301, Deivitto

Labels

bug
duplicate
2 (Med Risk)

Awards

1039.0663 USDC - $1,039.07

External Links

Lines of code

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L839-L845

Vulnerability details

Impact

the vetoer can burn the Veto Power by mistake without invoking the _burnVetoPower()

Proof of Concept

the vetoer could make a mistake by passing address(0x0) when he tries to set a new vetoer address by invoking _setVetoer()

Add check for address(0x0)

#0 - davidbrai

2022-08-29T15:42:43Z

Duplicate of #315

add a require() rather than if() and update the function pattern

This function have a bad pattern castRefundableVoteInternal() By updating it, castRefundableVote() and castRefundableVoteWithReason() will get a clear response

Finding

File: contracts/governance/NounsDAOLogicV2.sol uint256 startGas = gasleft(); uint96 votes = castVoteInternal(msg.sender, proposalId, support); emit VoteCast(msg.sender, proposalId, support, votes, reason); if (votes > 0) { _refundGas(startGas); }

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L533-L544

The function will be like this

uint96 votes = castVoteInternal(msg.sender, proposalId, support); require(votes > 0, ' ….'); emit VoteCast(msg.sender, proposalId, support, votes, reason); _refundGas(gasleft());

castVoteWithReason() move it up

For better flow (when someone reads the code) and organization ## Finding

File: contracts/governance/NounsDAOLogicV2.sol function castVoteWithReason( uint256 proposalId, uint8 support, string calldata reason ) external { emit VoteCast(msg.sender, proposalId, support, castVoteInternal(msg.sender, proposalId, support), reason); }

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L552-L558

it better to move this function up to line 492 after castVote()

add check for votes == 0

any user can try to vote without having any votes so the will be receipt.hasVoted = trueand receipt.support will take one of these numbers 2||1||0 and receipt.votes = 0

Finding

File: contracts/governance/NounsDAOLogicV2.sol uint96 votes = nouns.getPriorVotes(voter, proposalCreationBlock(proposal));

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L600

Add a require

require(votes >0, '...');

So it will be like this

uint96 votes = nouns.getPriorVotes(voter, proposalCreationBlock(proposal)); require(votes >0, '...');

#check if the _withdraw() is successful or not

Finding

File: contracts/governance/NounsDAOLogicV2.sol (bool sent, ) = msg.sender.call{ value: amount }('');

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L789

Add check

(bool success, ) = msg.sender.call{amount}(""); require(success, "Transfer failed.");

Missing checks for address(0x0)

Finding

File: contracts/governance/NounsDAOLogicV2.sol function _setPendingAdmin(address newPendingAdmin) external { // Check caller = admin require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only'); // Save current value, if any, for inclusion in log address oldPendingAdmin = pendingAdmin; // Store pendingAdmin with value newPendingAdmin pendingAdmin = newPendingAdmin; // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin) emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin); }

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L799-L811

Add this check

require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only'); require(newPendingAdmin != address(0x0) , "...");

add a timeLock for _burnVetoPower()

it is better to add a timeLock here for more trusting

Finding

File: contracts/governance/NounsDAOLogicV2.sol function _burnVetoPower() public { // Check caller is pendingAdmin and pendingAdmin ≠ address(0) require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only'); _setVetoer(address(0)); }

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L851-L856

add a timeLock

this function needs to be external

Finding

File: contracts/governance/NounsDAOLogicV2.sol function _burnVetoPower() public {...

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L851

wrong comment

Finding

File: contracts/governance/NounsDAOLogicV2.sol // Check caller is pendingAdmin and pendingAdmin ≠ address(0) require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L852

unnecessarily check proposalCreationBlock()

The if (proposal.creationBlock == 0) statement on proposalCreationBlock() will will never be true That is because it’s an internal function called only by castVoteInternal() which granite the proposalId is exists by invoking state() Here require(state(proposalId) == ProposalState.Active, '...'); So we can delete proposalCreationBlock() if they need it only with castVoteInternal() and suffice with proposal.creationBlock directly

Finding

File: contracts/governance/NounsDAOLogicV2.sol if (proposal.creationBlock == 0) { return proposal.startBlock - votingDelay; }

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L867-L869

#unnecessarily check quorumVotes() if (proposal.totalSupply == 0) this only will be true if the proposalIdis doesn't exist yet and this return proposal.quorumVotes will be always 0

Finding

File: contracts/governance/NounsDAOLogicV2.sol if (proposal.totalSupply == 0) { return proposal.quorumVotes; }

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L879-L881

add check for the proposalIdis exists or not

require(proposalCount >= proposalId, 'NounsDAO::state: invalid proposal id');

need to unify styles between _setDynamicQuorumParams() and _setMaxQuorumVotesBPS(), _setMinQuorumVotesBPS()

_setDynamicQuorumParams() use < and > to check the values and _setMaxQuorumVotesBPS(), _setMinQuorumVotesBPS() use <= and >=

Finding

File: contracts/governance/NounsDAOLogicV2.sol function _setMinQuorumVotesBPS(uint16 newMinQuorumVotesBPS) external… function _setMaxQuorumVotesBPS(uint16 newMaxQuorumVotesBPS) external {... function _setDynamicQuorumParams(...

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L673-L720 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L748-L781

One of them needs to edit also update their comments to be consistent

Update the castRefundableVoteInternal() function pattern

This function have a bad pattern castRefundableVoteInternal() By updating it, castRefundableVote() and castRefundableVoteWithReason() will save more gas

Finding

File: contracts/governance/NounsDAOLogicV2.sol uint256 startGas = gasleft(); uint96 votes = castVoteInternal(msg.sender, proposalId, support); emit VoteCast(msg.sender, proposalId, support, votes, reason); if (votes > 0) { _refundGas(startGas); }

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L533-L544

The function will be like this

uint96 votes = castVoteInternal(msg.sender, proposalId, support); require(votes > 0, ' ….'); emit VoteCast(msg.sender, proposalId, support, votes, reason); _refundGas(gasleft());

Duplicated require()/revert() checks should be refactored to a modifier

Finding

File: contracts/governance/NounsDAOLogicV2.sol require(msg.sender == admin, 'NounsDAO::_setMaxQuorumVotesBPS: admin only'); if (msg.sender != admin) { revert AdminOnly(); } require(msg.sender == vetoer, 'NounsDAO::_setVetoer: vetoer only');

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L622 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L638 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L655 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L674 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L702 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L727 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L753-L755 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L784-L786 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L801 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L840 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L853

Replace them with a modifier

no need for the second part on this check

Finding

File: contracts/governance/NounsDAOLogicV2.sol require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L819

use Memory rather than storage

Finding

File: contracts/governance/NounsDAOLogicV2.sol Proposal storage proposal = _proposals[proposalId];

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L878

Using calldata instead of memory for read-only arguments in external functions saves gas

Finding

File: contracts/governance/NounsDAOLogicV2.sol function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) {

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L1018

zero-initialization

Leaving the variables on the default variable bool = false and unit = 0 will save you some gas

Finding

File: contracts/governance/NounsDAOLogicV2.sol newProposal.eta = 0; newProposal.forVotes = 0; newProposal.againstVotes = 0; newProposal.abstainVotes = 0; newProposal.canceled = false; newProposal.executed = false; newProposal.vetoed = false;

https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L238-L243 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L231

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter