Platform: Code4rena
Start Date: 03/07/2023
Pot Size: $100,000 USDC
Total HM: 4
Participants: 36
Period: 10 days
Judge: gzeon
Total Solo HM: 3
Id: 257
League: ETH
Rank: 22/36
Findings: 1
Award: $58.98
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: shark
Also found by: 0xMilenov, Bauchibred, Kaysoft, MohammedRizwan, codegpt, descharre, fatherOfBlocks, ihtishamsudo, klau5, koxuan, nadin
58.9765 USDC - $58.98
ID | Finding | Instances |
---|---|---|
L-01 | initialize() can be called again if timelock is set to 0 | 1 |
L-02 | Admin can be set directly using _setTimelocksAndAdmin() | 1 |
ID | Finding | Instances |
---|---|---|
N-01 | Typos | 1 |
N-02 | Missing max value for lastMinuteWindowInBlocks | 1 |
N-03 | Be consistent with using if and require | 1 |
N-04 | Ethereum gas costs might change in the future | 1 |
The initialize()
function in NounsDAOLogicV3 doesn't have an initializer modifier. Instead it checks address(ds.timelock) != address(0)
. This would be a good practice to only initialize once. However the admin can set the timelock address in _setTimelocksAndAdmin
. Since there is no 0 address check, the admin can set the timelock to 0 by mistake. When that happens, the NounsDAOLogicV3 can be initialized again.
function initialize( address timelock_, address nouns_, address forkEscrow_, address forkDAODeployer_, address vetoer_, NounsDAOParams calldata daoParams_, DynamicQuorumParams calldata dynamicQuorumParams_ ) public virtual { if (address(ds.timelock) != address(0)) revert CanOnlyInitializeOnce(); ... }
function _setTimelocksAndAdmin( NounsDAOStorageV3.StorageV3 storage ds, address timelock, address timelockV1, address admin ) external onlyAdmin(ds) { ds.timelock = INounsDAOExecutorV2(timelock); ds.timelockV1 = INounsDAOExecutor(timelockV1); ds.admin = admin; emit TimelocksAndAdminSet(timelock, timelockV1, admin); }
_setTimelocksAndAdmin()
The NounsDAOExecutor has a 2 step admin transfer with the functions setPendingAdmin()
and acceptAdmin()
. A 2-step ownership pattern is a good practice. However this can be bypassed when the admin calls _setTimelocksAndAdmin()
. This function allows to set the admin directly, which eliminates the purpose of the other 2 functions.
function _setTimelocksAndAdmin( NounsDAOStorageV3.StorageV3 storage ds, address timelock, address timelockV1, address admin ) external onlyAdmin(ds) { ds.timelock = INounsDAOExecutorV2(timelock); ds.timelockV1 = INounsDAOExecutor(timelockV1); ds.admin = admin; emit TimelocksAndAdminSet(timelock, timelockV1, admin); }
Consider removing the option to set an admin directly.
lastMinuteWindowInBlocks
When setting the lastMinuteWindowInBlocks, there is no min or max settable value. Missing a min value isn't a problem in this case. However missing a max value can lead to really long waiting times if it's set wrong. Consider adding a max value of a few days.
function _setLastMinuteWindowInBlocks(NounsDAOStorageV3.StorageV3 storage ds, uint32 newLastMinuteWindowInBlocks) external onlyAdmin(ds) { uint32 oldLastMinuteWindowInBlocks = ds.lastMinuteWindowInBlocks; ds.lastMinuteWindowInBlocks = newLastMinuteWindowInBlocks; emit LastMinuteWindowSet(oldLastMinuteWindowInBlocks, newLastMinuteWindowInBlocks); }
The NounsDAOV3Admin is not consistent when setting the variables and checking the min and max values. Some functions use if and some require. It can be confusing when you switch them up so it's best to always use the same.
L387: require( newMaxQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS_UPPER_BOUND, 'NounsDAO::_setMaxQuorumVotesBPS: invalid max quorum votes bps' ); L438: if ( newMinQuorumVotesBPS < MIN_QUORUM_VOTES_BPS_LOWER_BOUND || newMinQuorumVotesBPS > MIN_QUORUM_VOTES_BPS_UPPER_BOUND ) { revert InvalidMinQuorumVotesBPS(); }
When a vote gets refunded, there is a REFUND_BASE_GAS
of 36000 gas. This includes 7K for ETH transfer and 29K for general transaction overhead. However gas costs might change in the future. Leading to more or less refunding than necessary. A mitigation is to remove the so the admin can change the value.
#0 - c4-judge
2023-07-25T10:04:26Z
gzeon-c4 marked the issue as grade-b