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: 50/160
Findings: 2
Award: $52.34
π Selected for report: 0
π Solo Findings: 0
π 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
35.6777 USDC - $35.68
Consider renaming pos
in NounsDAOLogicV2._writeQuorumParamsCheckpoint
to len
in order to have the same notation as in NounsDAOLogicV2.getDynamicQuorumParamsAt
.
uint256 pos = quorumParamsCheckpoints.length;
uint256 len = quorumParamsCheckpoints.length;
Following the standard that have been used for the rest of the code safe32(block.number, 'block number exceeds 32 bits');
error message should be : safe32(block.number, 'NounsDAO::_writeQuorumParamsCheckpoint: block number exceeds 32 bits');
in _writeQuorumParamsCheckpoint
.
uint32 blockNumber = safe32(block.number, 'block number exceeds 32 bits');
receipt.hasVoted == false
can be refactor to !receipt.hasVoted
to improve readabilityIn NounsDAOLogicV1.sol
and NounsDAOLogicV2.sol
:
require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
could be written require(!receipt.hasVoted, 'NounsDAO::castVoteInternal: voter already voted');
require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
Implementation contracts should not use a floating pragma in order to ensure that the code has been tested and deployed with the same version.
NounsDAOLogicV1.sol
and NounsDAOLogicV2.sol
should not have a floating pragma.
NounsDAOLogicV2.sol
does not compile for version 0.8.6NounsDAOLogicV2.sol
does not compile for compiler version 0.8.6 because block.basefee has been added in version 0.8.7.
uint256 gasPrice = min(tx.gasprice, block.basefee + MAX_REFUND_PRIORITY_FEE);
In NounsDAOLogicV1.sol
and NounsDAOLogicV2.sol
the comment // Check caller is pendingAdmin and pendingAdmin β address(0)
is incorrect. It should be // Check if caller is vetoer
.
function _burnVetoPower() public { // Check caller is pendingAdmin and pendingAdmin β address(0) require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');
function _burnVetoPower() public { // Check caller is pendingAdmin and pendingAdmin β address(0) require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');
In NounsDAOLogicV2.sol
the comment // 4,000 basis points or 60%
is incorrect and should be // 6,000 basis points or 60%
instead.
uint256 public constant MAX_QUORUM_VOTES_BPS_UPPER_BOUND = 6_000; // 4,000 basis points or 60%
π 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
NounsDAOLogicV2.getDynamicQuorumParamsAt
After calling the implementation contract NounsDAOLogicV2.sol
from the proxy the initialize
function is called. In the initialize
function, _setDynamicQuorumParams
is called and set via dynamicQuorumParams_
the quorum parameters of the struct params
. Then the _writeQuorumParamsCheckpoint
function is called with params
as argument, since the array quorumParamsCheckpoints
still has a length 0 the else logic is executed and quorumParamsCheckpoints[0] is updated to current block as quorumParamsCheckpoints[0].fromBlock and the chosen parameters as quorumParamsCheckpoints[0].params.
Now when the getDynamicQuorumParamsAt
function is called (necessarily after the initialize function) the quorumParamsCheckpoints
array is not of length 0 anymore so len == 0
will never be true.
Assuming the initialize function being called right after NounsDAOLogicV2.sol
being implemented via the proxy contract, this left the code associated with the check unused.
if (len == 0) { return DynamicQuorumParams({ minQuorumVotesBPS: safe16(quorumVotesBPS), maxQuorumVotesBPS: safe16(quorumVotesBPS), quorumCoefficient: 0 }); }
NounsDAOLogicV1.sol
and NounsDAOLogicV2.sol
If the vetoer has the intention to βdestroy veto power foreverβ he will use _burnVetoPower()
which will check if caller is vetoer
and call _setVetoer(address(0))
to check (again) if caller is vetoer
and then assign vetoer
to address(0)
.
If the vetoer has the intention to change the vetoer he will use _setVetoer()
with the address he chooses as argument. No 0 address check can be made because the function has to be usable for the purpose of destroying veto power.
The _burnVetoPower() function is not useful. vetoer
can use _setVetoer(address(0)) directly to destroy veto power.