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
Rank: 16/160
Findings: 3
Award: $1,094.57
🌟 Selected for report: 0
🚀 Solo Findings: 0
the vetoer
can burn the Veto Power
by mistake without invoking the _burnVetoPower()
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
🌟 Selected for report: IllIllI
Also found by: 0bi, 0x040, 0x1337, 0x1f8b, 0xDjango, 0xNazgul, 0xNineDec, 0xRajeev, 0xSky, 0xSmartContract, 0xbepresent, 0xkatana, 0xmatt, 8olidity, Aymen0909, Bjorn_bug, Bnke0x0, CertoraInc, Ch_301, Chom, CodingNameKiki, Deivitto, DevABDee, DimitarDimitrov, Dravee, ElKu, Funen, GalloDaSballo, GimelSec, Guardian, Haruxe, JC, JansenC, Jeiwan, JohnSmith, KIntern_NA, Lambda, LeoS, Noah3o6, Olivierdem, R2, RaymondFam, Respx, ReyAdmirado, Rohan16, Rolezn, Ruhum, Saintcode_, Sm4rty, SooYa, Soosh, TomJ, Tomo, Trabajo_de_mates, Waze, _Adam, __141345__, ajtra, android69, asutorufos, auditor0517, berndartmueller, bobirichman, brgltd, c3phas, cRat1st0s, carlitox477, catchup, cccz, csanuragjain, d3e4, delfin454000, dipp, djxploit, durianSausage, erictee, exd0tpy, fatherOfBlocks, gogo, hyh, ladboy233, lukris02, mics, mrpathfindr, natzuu, oyc_109, p_crypt0, pashov, pauliax, pfapostol, prasantgupta52, rajatbeladiya, rbserver, ret2basic, rfa, robee, rokinot, rvierdiiev, sach1r0, saian, seyni, shenwilly, sikorico, simon135, sryysryy, sseefried, throttle, tnevler, tonisives, wagmi, xiaoming90, yixxas, z3s, zkhorse, zzzitron
38.8441 USDC - $38.84
require()
rather than if()
and update the function patternThis function have a bad pattern castRefundableVoteInternal()
By updating it, castRefundableVote()
and castRefundableVoteWithReason()
will get a clear response
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); }
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 upFor 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); }
it better to move this function up to line 492
after castVote()
votes == 0
any user can try to vote
without having any votes so the will be receipt.hasVoted = true
and receipt.support
will take one of these numbers 2||1||0
and receipt.votes = 0
File: contracts/governance/NounsDAOLogicV2.sol uint96 votes = nouns.getPriorVotes(voter, proposalCreationBlock(proposal));
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
File: contracts/governance/NounsDAOLogicV2.sol (bool sent, ) = msg.sender.call{ value: amount }('');
Add check
(bool success, ) = msg.sender.call{amount}(""); require(success, "Transfer failed.");
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); }
Add this check
require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only'); require(newPendingAdmin != address(0x0) , "...");
timeLock
for _burnVetoPower()
it is better to add a timeLock
here for more trusting
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)); }
add a timeLock
external
File: contracts/governance/NounsDAOLogicV2.sol function _burnVetoPower() public {...
File: contracts/governance/NounsDAOLogicV2.sol // Check caller is pendingAdmin and pendingAdmin ≠address(0) require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');
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
File: contracts/governance/NounsDAOLogicV2.sol if (proposal.creationBlock == 0) { return proposal.startBlock - votingDelay; }
#unnecessarily check quorumVotes()
if (proposal.totalSupply == 0)
this only will be true if the proposalId
is doesn't exist yet and this return proposal.quorumVotes
will be always 0
File: contracts/governance/NounsDAOLogicV2.sol if (proposal.totalSupply == 0) { return proposal.quorumVotes; }
add check for the proposalId
is exists or not
require(proposalCount >= proposalId, 'NounsDAO::state: invalid proposal id');
_setDynamicQuorumParams()
and _setMaxQuorumVotesBPS(), _setMinQuorumVotesBPS()
_setDynamicQuorumParams()
use <
and >
to check the values and _setMaxQuorumVotesBPS(), _setMinQuorumVotesBPS()
use <=
and >=
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
🌟 Selected for report: IllIllI
Also found by: 0x040, 0x1f8b, 0xDjango, 0xNazgul, 0xNineDec, 0xSmartContract, 0xbepresent, 0xc0ffEE, 0xkatana, 2997ms, ACai, Amithuddar, Aymen0909, Ben, BipinSah, Bjorn_bug, Bnke0x0, CertoraInc, Ch_301, Chom, CodingNameKiki, Deivitto, DevABDee, DimitarDimitrov, Diraco, Dravee, ElKu, EthLedger, Fitraldys, Funen, GalloDaSballo, GimelSec, Guardian, IgnacioB, JC, JohnSmith, Junnon, KIntern_NA, Lambda, LeoS, Noah3o6, Olivierdem, Polandia94, R2, Randyyy, RaymondFam, Respx, ReyAdmirado, Rohan16, RoiEvenHaim, Rolezn, Ruhum, SaharAP, Saintcode_, SerMyVillage, Shishigami, Sm4rty, SooYa, TomJ, Tomio, Tomo, Waze, Yiko, _Adam, __141345__, a12jmx, ajtra, ak1, bobirichman, brgltd, bulej93, c3phas, cRat1st0s, carlitox477, catchup, ch0bu, d3e4, delfin454000, djxploit, durianSausage, erictee, exolorkistis, fatherOfBlocks, francoHacker, gogo, hyh, ignacio, jag, joestakey, karanctf, ladboy233, lucacez, lukris02, m_Rassska, martin, medikko, mics, mrpathfindr, natzuu, newfork01, oyc_109, pauliax, peritoflores, pfapostol, prasantgupta52, rbserver, ret2basic, rfa, robee, rokinot, rotcivegaf, rvierdiiev, sach1r0, saian, samruna, seyni, shark, shr1ftyy, sikorico, simon135, sryysryy, tay054, tnevler, wagmi, zishansami
16.6568 USDC - $16.66
castRefundableVoteInternal()
function patternThis function have a bad pattern castRefundableVoteInternal()
By updating it, castRefundableVote()
and castRefundableVoteWithReason()
will save more gas
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); }
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());
require()/revert()
checks should be refactored to a modifierFile: 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
File: contracts/governance/NounsDAOLogicV2.sol require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
Memory
rather than storage
File: contracts/governance/NounsDAOLogicV2.sol Proposal storage proposal = _proposals[proposalId];
calldata
instead of memory
for read-only arguments in external
functions saves gasFile: contracts/governance/NounsDAOLogicV2.sol function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) {
Leaving the variables on the default variable bool = false
and unit = 0
will save you some gas
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