Nouns DAO contest - ajtra'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: 41/160

Findings: 2

Award: $52.42

๐ŸŒŸ Selected for report: 0

๐Ÿš€ Solo Findings: 0

Summary

Low

[L01] A floating pragma is set. [L02] A different pragma is set. [L03] Non reentrancy guard

Non Critical

[NC01] Use of block timestamp

Low

[L01] A floating pragma is set.

Description

The current pragma Solidity directive is "^0.8.6". It is recommended to specify a fixed compiler version to ensure that the bytecode produced does not vary between builds. This is especially important if you rely on bytecode-level verification of the code.

Mitigation

Lock the pragma version

Lines in the code

NounsDAOLogicV1.sol#L61 NounsDAOLogicV2.sol#L53 NounsDAOInterfaces.sol#L33 NounsDAOProxy.sol#L36 ERC721Checkpointable.sol#L35 ERC721Enumerable.sol#L28

[L02] A different pragma is set.

Description

The current pragma Solidity directive is "^0.8.6" but there is other contract ERC721Enumerable that is using a different pragma version "^0.8.0". It's cleaner to use the same versions.

Lines in the code

ERC721Enumerable.sol#L28

[L03] Non reentrancy guard

Description

A reentrancy in this case depend on the code of "implementation" in _fallback (NounsDAOProxy.sol) function but it's important to have a reentrancy guard in external functions when have an external call so fallback() and receive() should be protected.

Recommendation

It is recommended to follow the good security practices and apply necessary reentrancy prevention by utilizing the nonReentrant modifier from Openzeppelin Library to block possible re-entrancy.

Lines in the code

NounsDAOProxy.sol#L130-L132 NounsDAOProxy.sol#L138-L140 NounsDAOProxy.sol#L108

Non Critical

[NC01] Use of block timestamp

Description

Block timestamps have historically been used for a variety of applications, such as entropy for random numbers (see the Entropy Illusion for further details), locking funds for periods of time, and various state-changing conditional statements that are time-dependent. Miners have the ability to adjust timestamps slightly, which can prove to be dangerous if block timestamps are used incorrectly in smart contracts.

Recommendation

Block timestamps should not be used for entropy or generating random numbersโ€”i.e., they should not be the deciding factor (either directly or through some derivation) for winning a game or changing an important state.

Time-sensitive logic is sometimes required; e.g., for unlocking contracts (time-locking), completing an ICO after a few weeks, or enforcing expiry dates. It is sometimes recommended to use block.number and an average block time to estimate times; with a 10 second block time, 1 week equates to approximately, 60480 blocks. Thus, specifying a block number at which to change a contract state can be more secure, as miners are unable to easily manipulate the block number.

Lines in the code

NounsDAOLogicV1.sol#L280 NounsDAOLogicV1.sol#L438 NounsDAOLogicV2.sol#L291 NounsDAOLogicV2.sol#L449 ERC721Checkpointable.sol#L142

Index

[G01] Post-increment/decrement cost more gas than pre-increment/decrement [G02] Operatos <= or >= cost more gas than operators < or > [G03] Array length should not be looked up in every loop of a for-loop [G04] Require instead of && [G05] Multiplication/division by two should use bit shifting [G06] Change parameters from memory to calldata [G07] Use unchecked when it's not possible to overflow [G08] Require / Revert strings longer than 32 bytes cost extra gas [G09] Use custom errors rather than REVERT()/REQUIRE() strings to save deployment gas [G10] ABI.ENCODE() is less efficient than ABI.ENCODEPACKED() [G11] Don't compare boolean expressions to boolean literals [G12] Call to KECCAK256 should use IMMUTABLE rather than constant [G13] Using bools for storage incurs overhead [G14] Using private rather than public for constants [G15] Usage of uints/ints smaller than 32 Bytes (256 bits) incurs overhead [G16] Require() or Revert() statements that check input arguments should be at the top of the function [G17] Use a more recent version of solidity [G18] Initialize variables with default values are not needed [G19] Using storage instead of memory for structs/arrays [G20] Duplicated conditions should be refactored to a modifier or function to save deployment costs [G21] Declare variables just when it's going to be used [G22] Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate [G23] Tight variable packing [G24] Use bytes32 instead of string

Details

[G01] Post-increment/decrement cost more gas then pre-increment/decrement

Description

++var (--var) cost less gas than var++ (var--) especially in a loop.

Proof of concept

contract TestPost {
	function testPost() public {
		uint256 i;
		i++;
	}
}
contract TestPre {
	function testPre() public {
		uint256 i;
		++i;
	}
}
  • Transaction cost of testPost is 21333 gas
  • Transaction cost of testPre is 21328 gas
  • After the test it's possible to save 5 gas per ocurrence with this optimization.

Lines in the code

NounsDAOLogicV1.sol#L216 NounsDAOLogicV1.sol#L281 NounsDAOLogicV1.sol#L319 NounsDAOLogicV1.sol#L346 NounsDAOLogicV1.sol#L371 NounsDAOLogicV2.sol#L226 NounsDAOLogicV2.sol#L292 NounsDAOLogicV2.sol#L330 NounsDAOLogicV2.sol#L357 NounsDAOLogicV2.sol#L382 ERC721Checkpointable.sol#L141

[G02] Operatos <= or >= cost more gas than operators < or >

Description

Change all <= / >= operators for < / > and remember to increse / decrese in consecuence to maintain the logic (example, a <= b for a < b + 1)

Proof of concept

contract TestMaxEqual {

	function testMaxEqual() public {
		uint256 i = 1;
		if (i >= 1){
			i++;
		}
	}
}

contract TestMax {

	function TestMax() public {
		uint256 i = 1;
		if (i > 0){
			i++;
		}
	}
}
  • Transaction cost of TestMaxEqual is 21367 gas
  • Transaction cost of TestMax is 21364 gas
  • After the test it's possible to save 3 gas per ocurrence with this optimization.

Lines in the code

NounsDAOLogicV1.sol#L127 NounsDAOLogicV1.sol#L127 NounsDAOLogicV1.sol#L131 NounsDAOLogicV1.sol#L131 NounsDAOLogicV1.sol#L135 NounsDAOLogicV1.sol#L135 NounsDAOLogicV1.sol#L139 NounsDAOLogicV1.sol#L139 NounsDAOLogicV1.sol#L198 NounsDAOLogicV1.sol#L422 NounsDAOLogicV1.sol#L428 NounsDAOLogicV1.sol#L430 NounsDAOLogicV1.sol#L432 NounsDAOLogicV1.sol#L438 NounsDAOLogicV1.sol#L502 NounsDAOLogicV1.sol#L532 NounsDAOLogicV1.sol#L532 NounsDAOLogicV1.sol#L548 NounsDAOLogicV1.sol#L548 NounsDAOLogicV1.sol#L565 NounsDAOLogicV1.sol#L566 NounsDAOLogicV1.sol#L583 NounsDAOLogicV1.sol#L583 NounsDAOLogicV2.sol#L138 NounsDAOLogicV2.sol#L138 NounsDAOLogicV2.sol#L142 NounsDAOLogicV2.sol#L142 NounsDAOLogicV2.sol#L146 NounsDAOLogicV2.sol#L146 NounsDAOLogicV2.sol#L208 NounsDAOLogicV2.sol#L433 NounsDAOLogicV2.sol#L439 NounsDAOLogicV2.sol#L441 NounsDAOLogicV2.sol#L443 NounsDAOLogicV2.sol#L449 NounsDAOLogicV2.sol#L594 NounsDAOLogicV2.sol#L624 NounsDAOLogicV2.sol#L624 NounsDAOLogicV2.sol#L640 NounsDAOLogicV2.sol#L640 NounsDAOLogicV2.sol#L657 NounsDAOLogicV2.sol#L658 NounsDAOLogicV2.sol#L678 NounsDAOLogicV2.sol#L679 NounsDAOLogicV2.sol#L683 NounsDAOLogicV2.sol#L706 NounsDAOLogicV2.sol#L710 NounsDAOLogicV2.sol#L935 NounsDAOLogicV2.sol#L1019 ERC721Checkpointable.sol#L142 ERC721Checkpointable.sol#L172 ERC721Checkpointable.sol#L269 ERC721Checkpointable.sol#L278

[G03] Array length should not be looked up in every loop of a for-loop

Description

Storage array length checks incur an extra Gwarmaccess (100 gas) per loop. Store the array length in a variable and use it in the for loop helps to save gas.

Proof of concept

contract TestForLength {
	function testArrayLength() public {
		uint256[] memory array = new uint256[](10);
		for(uint256 i; i < array.length; ){
			++i;
		}
	}
}
contract TestForCachLength {
	function testArrayLength() public {
		uint256[] memory array = new uint256[](10);
		uint256 arrayLen = array.length;
		for(uint256 i; i < arrayLen; ){
			++i;
		}
	}
}
  • Transaction cost of TestForLength is 23217 gas
  • Transaction cost of TestForCachLength is 23200 gas
  • After the test it's possible to save 17 gas in this loop so this mean ~2 gas per loop is saved with this optimization in the test case of a local array.

Lines in the code

NounsDAOLogicV1.sol#L281 NounsDAOLogicV1.sol#L319 NounsDAOLogicV1.sol#L346 NounsDAOLogicV1.sol#L371 NounsDAOLogicV2.sol#L292 NounsDAOLogicV2.sol#L330 NounsDAOLogicV2.sol#L357 NounsDAOLogicV2.sol#L382

[G04] Require instead of &&

Description

Split of conditions of a require sentence in different requires sentences can save gas

Proof of concept

contract TestRequireWithout {
	function TestRequireWithoutAnd () public {
		uint256 a = 1;
		uint256 b = 2;
		require(a < 2, 'a');
		require(b < 3, 'b');
	}
}

contract TestRequireWith {
	function TestRequireWithAnd () public{
		uint256 a = 1;
		uint256 b = 2;
		require(a < 2 && b < 3, 'ab');
	}
}
  • Transaction cost of TestRequireWithout is 21258 gas
  • Transaction cost of TestRequireWith is 21266 gas
  • After the test it's possible to save 8 gas with this optimization per ocurrence.

Lines in the code

NounsDAOLogicV1.sol#L617 NounsDAOLogicV2.sol#L138 NounsDAOLogicV2.sol#L142 NounsDAOLogicV2.sol#L146 NounsDAOLogicV2.sol#L201-L206 NounsDAOLogicV2.sol#L624 NounsDAOLogicV2.sol#L640 NounsDAOLogicV2.sol#L657-L658 NounsDAOLogicV2.sol#L678-L679 NounsDAOLogicV2.sol#L819

[G05] Multiplication/division by two should use bit shifting

Description

<x> * 2 is equivalent to <x> << 1 and <x> / 2 is the same as <x> >> 1. The MUL and DIV opcodes cost 5 gas, whereas SHL and SHR only cost 3 gas

Proof of concept

contract TestDiv2 {
	function TestDivBy2 () public returns (uint256){
		uint256 i = 4;
		i = i / 2;
		return i;
	}
}

contract TestDivShift {
	function TestDivByShift () public returns (uint256){
		uint256 i = 4;
		i = i >> 1;
		return i;
	}
}
  • Transaction cost of TestDiv2 is 21581 gas
  • Transaction cost of TestDivShift is 21409 gas
  • After the test it's possible to save 172 gas with this optimization per ocurrence.

Lines in the code

NounsDAOLogicV2.sol#L951 ERC721Checkpointable.sol#L184

[G06] Change parameters from memory to calldata

Description

Changing memory parameters to calldata and public to external it's possible to save gas.

Proof of concept

contract TestCallData {
	function Test (uint256[] calldata param) external{
	}
}

contract TestMemory {
	function Test (uint256[] memory param) public{
	}
}
  • Transaction cost of TestCallData is 22496 gas
  • Transaction cost of TestMemory is 24104 gas
  • After the test it's possible to save 1608 gas per ocurrence with this optimization.

Lines in the code

NounsDAOLogicV2.sol#L185-L190 NounsDAOLogicV1.sol#L174-L180

[G07] Use unchecked when it's not possible to overflow

Description

The default โ€œcheckedโ€ behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected. if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an unchecked block will save gas.

For all for-loops in the code it is possible to change as the following example.

for (uint256 i;i < X;){
	-- code --
	unchecked
	{
		++i;
	}
}

Proof of concept

contract TestWithoutUnchecked {
	function Test() public {
		for(uint256 i; i < 10; ){
			++i;
		}
	}
}

contract TestWitUnchecked {
	function Test() public {
		for(uint256 i; i < 10; ){
			unchecked {
				++i;
			}
		}
	}
}
  • Transaction cost of TestWithoutUnchecked is 22958 gas
  • Transaction cost of TestWitUnchecked is 21728 gas
  • After the test it's possible to save 1230 gas in this loop so this mean 123 gas per loop is saved with this optimization.

Lines in the code

NounsDAOLogicV1.sol#L281 NounsDAOLogicV1.sol#L319 NounsDAOLogicV1.sol#L346 NounsDAOLogicV1.sol#L371 NounsDAOLogicV2.sol#L292 NounsDAOLogicV2.sol#L330 NounsDAOLogicV2.sol#L357 NounsDAOLogicV2.sol#L382

[G08] Require / Revert strings longer than 32 bytes cost extra gas

Description

Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas

Lines in the code

NounsDAOLogicV1.sol#L122 NounsDAOLogicV1.sol#L123 NounsDAOLogicV1.sol#L124 NounsDAOLogicV1.sol#L125 NounsDAOLogicV1.sol#L126 NounsDAOLogicV1.sol#L130 NounsDAOLogicV1.sol#L134 NounsDAOLogicV1.sol#L138 NounsDAOLogicV1.sol#L187 NounsDAOLogicV1.sol#L191 NounsDAOLogicV1.sol#L197 NounsDAOLogicV1.sol#L198 NounsDAOLogicV1.sol#L203 NounsDAOLogicV1.sol#L207 NounsDAOLogicV1.sol#L275 NounsDAOLogicV1.sol#L301 NounsDAOLogicV1.sol#L313 NounsDAOLogicV1.sol#L336 NounsDAOLogicV1.sol#L339 NounsDAOLogicV1.sol#L364 NounsDAOLogicV1.sol#L366 NounsDAOLogicV1.sol#L422 NounsDAOLogicV1.sol#L485 NounsDAOLogicV1.sol#L501 NounsDAOLogicV1.sol#L502 NounsDAOLogicV1.sol#L505 NounsDAOLogicV1.sol#L530 NounsDAOLogicV1.sol#L531 NounsDAOLogicV1.sol#L546 NounsDAOLogicV1.sol#L547 NounsDAOLogicV1.sol#L563 NounsDAOLogicV1.sol#L564 NounsDAOLogicV1.sol#L581 NounsDAOLogicV1.sol#L582 NounsDAOLogicV1.sol#L599 NounsDAOLogicV1.sol#L617 NounsDAOLogicV1.sol#L638 NounsDAOLogicV1.sol#L651 NounsDAOLogicV2.sol#L133 NounsDAOLogicV2.sol#L134 NounsDAOLogicV2.sol#L135 NounsDAOLogicV2.sol#L136 NounsDAOLogicV2.sol#L137 NounsDAOLogicV2.sol#L141 NounsDAOLogicV2.sol#L145 NounsDAOLogicV2.sol#L197 NounsDAOLogicV2.sol#L201 NounsDAOLogicV2.sol#L207 NounsDAOLogicV2.sol#L208 NounsDAOLogicV2.sol#L213 NounsDAOLogicV2.sol#L217 NounsDAOLogicV2.sol#L286 NounsDAOLogicV2.sol#L305 NounsDAOLogicV2.sol#L312 NounsDAOLogicV2.sol#L324 NounsDAOLogicV2.sol#L347 NounsDAOLogicV2.sol#L350 NounsDAOLogicV2.sol#L375 NounsDAOLogicV2.sol#L377 NounsDAOLogicV2.sol#L433 NounsDAOLogicV2.sol#L577 NounsDAOLogicV2.sol#L593 NounsDAOLogicV2.sol#L594 NounsDAOLogicV2.sol#L597 NounsDAOLogicV2.sol#L622 NounsDAOLogicV2.sol#L623 NounsDAOLogicV2.sol#L638 NounsDAOLogicV2.sol#L639 NounsDAOLogicV2.sol#L655 NounsDAOLogicV2.sol#L656 NounsDAOLogicV2.sol#L674 NounsDAOLogicV2.sol#L677 NounsDAOLogicV2.sol#L682 NounsDAOLogicV2.sol#L702 NounsDAOLogicV2.sol#L705 NounsDAOLogicV2.sol#L709 NounsDAOLogicV2.sol#L727 NounsDAOLogicV2.sol#L801 NounsDAOLogicV2.sol#L819 NounsDAOLogicV2.sol#L840 NounsDAOLogicV2.sol#L853 NounsDAOProxy.sol#L79 NounsDAOProxy.sol#L80 ERC721Checkpointable.sol#L140 ERC721Checkpointable.sol#L141 ERC721Checkpointable.sol#L142 ERC721Checkpointable.sol#L164 ERC721Enumerable.sol#L62 ERC721Enumerable.sol#L77

[G09] Use custom errors rather than REVERT()/REQUIRE() strings to save deployment gas

Description

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hitby avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas.

Lines in the code

NounsDAOLogicV1.sol#L122 NounsDAOLogicV1.sol#L123 NounsDAOLogicV1.sol#L124 NounsDAOLogicV1.sol#L125 NounsDAOLogicV1.sol#L126 NounsDAOLogicV1.sol#L130 NounsDAOLogicV1.sol#L134 NounsDAOLogicV1.sol#L138 NounsDAOLogicV1.sol#L187 NounsDAOLogicV1.sol#L191 NounsDAOLogicV1.sol#L197 NounsDAOLogicV1.sol#L198 NounsDAOLogicV1.sol#L203 NounsDAOLogicV1.sol#L207 NounsDAOLogicV1.sol#L275 NounsDAOLogicV1.sol#L301 NounsDAOLogicV1.sol#L313 NounsDAOLogicV1.sol#L336 NounsDAOLogicV1.sol#L339 NounsDAOLogicV1.sol#L364 NounsDAOLogicV1.sol#L365 NounsDAOLogicV1.sol#L366 NounsDAOLogicV1.sol#L422 NounsDAOLogicV1.sol#L485 NounsDAOLogicV1.sol#L501 NounsDAOLogicV1.sol#L502 NounsDAOLogicV1.sol#L505 NounsDAOLogicV1.sol#L530 NounsDAOLogicV1.sol#L531 NounsDAOLogicV1.sol#L546 NounsDAOLogicV1.sol#L547 NounsDAOLogicV1.sol#L563 NounsDAOLogicV1.sol#L564 NounsDAOLogicV1.sol#L581 NounsDAOLogicV1.sol#L582 NounsDAOLogicV1.sol#L599 NounsDAOLogicV1.sol#L617 NounsDAOLogicV1.sol#L638 NounsDAOLogicV1.sol#L651 NounsDAOLogicV2.sol#L133 NounsDAOLogicV2.sol#L134 NounsDAOLogicV2.sol#L135 NounsDAOLogicV2.sol#L136 NounsDAOLogicV2.sol#L137 NounsDAOLogicV2.sol#L141 NounsDAOLogicV2.sol#L145 NounsDAOLogicV2.sol#L197 NounsDAOLogicV2.sol#L201 NounsDAOLogicV2.sol#L207 NounsDAOLogicV2.sol#L208 NounsDAOLogicV2.sol#L213 NounsDAOLogicV2.sol#L217 NounsDAOLogicV2.sol#L286 NounsDAOLogicV2.sol#L312 NounsDAOLogicV2.sol#L324 NounsDAOLogicV2.sol#L347 NounsDAOLogicV2.sol#L350 NounsDAOLogicV2.sol#L375 NounsDAOLogicV2.sol#L376 NounsDAOLogicV2.sol#L377 NounsDAOLogicV2.sol#L433 NounsDAOLogicV2.sol#L577 NounsDAOLogicV2.sol#L593 NounsDAOLogicV2.sol#L594 NounsDAOLogicV2.sol#L597 NounsDAOLogicV2.sol#L622 NounsDAOLogicV2.sol#L623 NounsDAOLogicV2.sol#L638 NounsDAOLogicV2.sol#L639 NounsDAOLogicV2.sol#L655 NounsDAOLogicV2.sol#L656 NounsDAOLogicV2.sol#L674 NounsDAOLogicV2.sol#L677 NounsDAOLogicV2.sol#L682 NounsDAOLogicV2.sol#L702 NounsDAOLogicV2.sol#L705 NounsDAOLogicV2.sol#L709 NounsDAOLogicV2.sol#L727 NounsDAOLogicV2.sol#L801 NounsDAOLogicV2.sol#L819 NounsDAOLogicV2.sol#L840 NounsDAOLogicV2.sol#L853 NounsDAOLogicV2.sol#L1019 NounsDAOProxy.sol#L79 NounsDAOProxy.sol#L80 ERC721Checkpointable.sol#L140 ERC721Checkpointable.sol#L141 ERC721Checkpointable.sol#L142 ERC721Checkpointable.sol#L164 ERC721Checkpointable.sol#L254 ERC721Checkpointable.sol#L259 ERC721Checkpointable.sol#L269 ERC721Checkpointable.sol#L278 ERC721Enumerable.sol#L62 ERC721Enumerable.sol#L77

[G10] ABI.ENCODE() is less efficient than ABI.ENCODEPACKED()

Lines in the code

NounsDAOLogicV1.sol#L302 NounsDAOLogicV1.sol#L480 NounsDAOLogicV1.sol#L482 NounsDAOLogicV2.sol#L313 NounsDAOLogicV2.sol#L572 NounsDAOLogicV2.sol#L574 ERC721Checkpointable.sol#L135 ERC721Checkpointable.sol#L137

[G11] Don't compare boolean expressions to boolean literals

Description

if (<x> == true) => if (<x>), if (<x> == false) => if (!<x>)

Lines in the code

NounsDAOLogicV1.sol#L505 NounsDAOLogicV2.sol#L597

[G12] Call to KECCAK256 should use IMMUTABLE rather than constant

Description

Expressions for constant values such as a call to KECCAK256 should use IMMUTABLE rather than constant

Lines in the code

NounsDAOLogicV1.sol#L97 NounsDAOLogicV1.sol#L101 NounsDAOLogicV2.sol#L101 NounsDAOLogicV2.sol#L105 ERC721Checkpointable.sol#L59 ERC721Checkpointable.sol#L63

[G13] Using bools for storage incurs overhead

Description

Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from 'false' to 'true', after having been 'true' in the past

Lines in the code

NounsDAOInterfaces.sol#L204 NounsDAOInterfaces.sol#L206 NounsDAOInterfaces.sol#L208 NounsDAOInterfaces.sol#L216 NounsDAOInterfaces.sol#L304 NounsDAOInterfaces.sol#L306 NounsDAOInterfaces.sol#L308 NounsDAOInterfaces.sol#L320 NounsDAOInterfaces.sol#L390 NounsDAOInterfaces.sol#L392 NounsDAOInterfaces.sol#L394

[G14] Using private rather than public for constants, saves gas

Description

The value can be read from the verified contract source code if desired. The savings are due to the compiler not having to create a non-paid getter function for the deployment call data, nor adding another entry to the method ID table.

Lines in the code

NounsDAOLogicV1.sol#L67 NounsDAOLogicV1.sol#L70 NounsDAOLogicV1.sol#L73 NounsDAOLogicV1.sol#L76 NounsDAOLogicV1.sol#L79 NounsDAOLogicV1.sol#L82 NounsDAOLogicV1.sol#L85 NounsDAOLogicV1.sol#L88 NounsDAOLogicV1.sol#L91 NounsDAOLogicV1.sol#L94 NounsDAOLogicV1.sol#L97 NounsDAOLogicV1.sol#L101 NounsDAOLogicV2.sol#L59 NounsDAOLogicV2.sol#L62 NounsDAOLogicV2.sol#L65 NounsDAOLogicV2.sol#L68 NounsDAOLogicV2.sol#L71 NounsDAOLogicV2.sol#L74 NounsDAOLogicV2.sol#L77 NounsDAOLogicV2.sol#L80 NounsDAOLogicV2.sol#L83 NounsDAOLogicV2.sol#L86 NounsDAOLogicV2.sol#L89 NounsDAOLogicV2.sol#L92 NounsDAOLogicV2.sol#L95 NounsDAOLogicV2.sol#L98 NounsDAOLogicV2.sol#L101 NounsDAOLogicV2.sol#L105 ERC721Checkpointable.sol#L41 ERC721Checkpointable.sol#L59 ERC721Checkpointable.sol#L63

[G15] Usage of uints/ints smaller than 32 Bytes (256 bits) incurs overhead

Description

When using elements that are smaller than 32 bytes, your contract's gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size. Use a larger size then downcast where needed.

Lines in the code

NounsDAOLogicV1.sol#L101 NounsDAOLogicV1.sol#L450 NounsDAOLogicV1.sol#L462 NounsDAOLogicV1.sol#L474 NounsDAOLogicV1.sol#L475 NounsDAOLogicV1.sol#L499 NounsDAOLogicV1.sol#L500 NounsDAOLogicV1.sol#L508 NounsDAOLogicV2.sol#L105 NounsDAOLogicV2.sol#L489 NounsDAOLogicV2.sol#L503 NounsDAOLogicV2.sol#L520 NounsDAOLogicV2.sol#L535 NounsDAOLogicV2.sol#L539 NounsDAOLogicV2.sol#L554 NounsDAOLogicV2.sol#L566 NounsDAOLogicV2.sol#L567 NounsDAOLogicV2.sol#L591 NounsDAOLogicV2.sol#L592 NounsDAOLogicV2.sol#L600 NounsDAOLogicV2.sol#L673 NounsDAOLogicV2.sol#L687 NounsDAOLogicV2.sol#L701 NounsDAOLogicV2.sol#L714 NounsDAOLogicV2.sol#L726 NounsDAOLogicV2.sol#L730 NounsDAOLogicV2.sol#L749 NounsDAOLogicV2.sol#L750 NounsDAOLogicV2.sol#L751 NounsDAOLogicV2.sol#L923 NounsDAOLogicV2.sol#L965 NounsDAOLogicV2.sol#L966 NounsDAOLogicV2.sol#L1018 NounsDAOLogicV2.sol#L1019 NounsDAOLogicV2.sol#L1020 NounsDAOLogicV2.sol#L1023 NounsDAOLogicV2.sol#L1024 NounsDAOLogicV2.sol#L1027 NounsDAOInterfaces.sol#L70 NounsDAOInterfaces.sol#L111 NounsDAOInterfaces.sol#L114 NounsDAOInterfaces.sol#L117 NounsDAOInterfaces.sol#L218 NounsDAOInterfaces.sol#L220 NounsDAOInterfaces.sol#L322 NounsDAOInterfaces.sol#L324 NounsDAOInterfaces.sol#L352 NounsDAOInterfaces.sol#L354 NounsDAOInterfaces.sol#L357 NounsDAOInterfaces.sol#L363 NounsDAOInterfaces.sol#L437 NounsDAOInterfaces.sol#L439 ERC721Checkpointable.sol#L41 ERC721Checkpointable.sol#L48 ERC721Checkpointable.sol#L49 ERC721Checkpointable.sol#L53 ERC721Checkpointable.sol#L56 ERC721Checkpointable.sol#L79 ERC721Checkpointable.sol#L130 ERC721Checkpointable.sol#L151 ERC721Checkpointable.sol#L152 ERC721Checkpointable.sol#L163 ERC721Checkpointable.sol#L166 ERC721Checkpointable.sol#L181 ERC721Checkpointable.sol#L182 ERC721Checkpointable.sol#L184 ERC721Checkpointable.sol#L205 ERC721Checkpointable.sol#L213 ERC721Checkpointable.sol#L217 ERC721Checkpointable.sol#L218 ERC721Checkpointable.sol#L219 ERC721Checkpointable.sol#L224 ERC721Checkpointable.sol#L225 ERC721Checkpointable.sol#L226 ERC721Checkpointable.sol#L234 ERC721Checkpointable.sol#L235 ERC721Checkpointable.sol#L236 ERC721Checkpointable.sol#L238 ERC721Checkpointable.sol#L253 ERC721Checkpointable.sol#L255 ERC721Checkpointable.sol#L258 ERC721Checkpointable.sol#L260 ERC721Checkpointable.sol#L264 ERC721Checkpointable.sol#L265 ERC721Checkpointable.sol#L267 ERC721Checkpointable.sol#L268 ERC721Checkpointable.sol#L274 ERC721Checkpointable.sol#L275 ERC721Checkpointable.sol#L277

[G16] require() or revert() statements that check input arguments should be at the top of the function

Description

Checks involving constants should precede checks involving state variables, function calls, and computations. If you do these checks at the top, the function can return before wasting Gcoldsload (2100 Gas*), and in the worst case, can revert.

Lines in the code

ERC721Checkpointable.sol#L142

[G17] Use a more recent version of solidity

Description

Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value.

Lines in the code

NounsDAOLogicV1.sol#L61 NounsDAOLogicV2.sol#L53 NounsDAOInterfaces.sol#L33 NounsDAOProxy.sol#L36 ERC721Checkpointable.sol#L35 ERC721Enumerable.sol#L28

[G18] Initialize variables with default values are not needed

Description

If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address, etc). Explicitly initializing it with its default value is an anti-pattern and wastes gas (around 3 gas per instance).

Lines in the code

NounsDAOLogicV1.sol#L281 NounsDAOLogicV1.sol#L319 NounsDAOLogicV1.sol#L346 NounsDAOLogicV1.sol#L371 NounsDAOLogicV2.sol#L292 NounsDAOLogicV2.sol#L330 NounsDAOLogicV2.sol#L357 NounsDAOLogicV2.sol#L382 NounsDAOLogicV2.sol#L948 ERC721Checkpointable.sol#L181

[G19] Using storage instead of memory for structs/arrays

Description

When retrieving data from a memory location, assigning the data to a memory variable causes all fields of the struct/array to be read from memory, resulting in a Gcoldsload (2100 gas) for each field of the struct/array. When reading fields from new memory variables, they cause an extra MLOAD instead of a cheap stack read. Rather than declaring a variable with the memory keyword, it is much cheaper to declare a variable with the storage keyword and cache all fields that need to be read again in a stack variable, because the fields actually read will only result in a Gcoldsload. The only case where the entire struct/array is read into a memory variable is when the entire struct/array is returned by a function, passed to a function that needs memory, or when the array/struct is read from another store array/struc.

Lines in the code

NounsDAOLogicV2.sol#L952 ERC721Checkpointable.sol#L185

[G20] Duplicated conditions should be refactored to a modifier or function to save deployment costs

Lines in the code

Admin Only

NounsDAOLogicV1.sol#L123 NounsDAOLogicV1.sol#L530 NounsDAOLogicV1.sol#L546 NounsDAOLogicV1.sol#L563 NounsDAOLogicV1.sol#L581 NounsDAOLogicV1.sol#L599 NounsDAOLogicV2.sol#L134 NounsDAOLogicV2.sol#L622 NounsDAOLogicV2.sol#L638 NounsDAOLogicV2.sol#L655 NounsDAOLogicV2.sol#L674 NounsDAOLogicV2.sol#L702 NounsDAOLogicV2.sol#L727 NounsDAOLogicV2.sol#L801

Vetoer Only

NounsDAOLogicV1.sol#L365 NounsDAOLogicV1.sol#L638 NounsDAOLogicV1.sol#L651 NounsDAOLogicV2.sol#L376 NounsDAOLogicV2.sol#L840 NounsDAOLogicV2.sol#L853

[G21] Declare variables just when it's going to be used

Description

If a variable is declared and it's going to be used after If's or require sentences that can revert the transaction then move the declaration after these statements to save gas when the condition's of If's or require will be true.

    function _setMinQuorumVotesBPS(uint16 newMinQuorumVotesBPS) external {
        require(msg.sender == admin, 'NounsDAO::_setMinQuorumVotesBPS: admin only');
-       DynamicQuorumParams memory params = getDynamicQuorumParamsAt(block.number);

        require(
            newMinQuorumVotesBPS >= MIN_QUORUM_VOTES_BPS_LOWER_BOUND &&
                newMinQuorumVotesBPS <= MIN_QUORUM_VOTES_BPS_UPPER_BOUND,
            'NounsDAO::_setMinQuorumVotesBPS: invalid min quorum votes bps'
        );
+       DynamicQuorumParams memory params = getDynamicQuorumParamsAt(block.number);		
        require(
            newMinQuorumVotesBPS <= params.maxQuorumVotesBPS,
            'NounsDAO::_setMinQuorumVotesBPS: min quorum votes bps greater than max'
        );

        uint16 oldMinQuorumVotesBPS = params.minQuorumVotesBPS;
        params.minQuorumVotesBPS = newMinQuorumVotesBPS;

        _writeQuorumParamsCheckpoint(params);

        emit MinQuorumVotesBPSSet(oldMinQuorumVotesBPS, newMinQuorumVotesBPS);
    }
    function _setMaxQuorumVotesBPS(uint16 newMaxQuorumVotesBPS) external {
        require(msg.sender == admin, 'NounsDAO::_setMaxQuorumVotesBPS: admin only');
-       DynamicQuorumParams memory params = getDynamicQuorumParamsAt(block.number);

        require(
            newMaxQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS_UPPER_BOUND,
            'NounsDAO::_setMaxQuorumVotesBPS: invalid max quorum votes bps'
        );
+       DynamicQuorumParams memory params = getDynamicQuorumParamsAt(block.number);		
        require(
            params.minQuorumVotesBPS <= newMaxQuorumVotesBPS,
            'NounsDAO::_setMaxQuorumVotesBPS: min quorum votes bps greater than max'
        );

        uint16 oldMaxQuorumVotesBPS = params.maxQuorumVotesBPS;
        params.maxQuorumVotesBPS = newMaxQuorumVotesBPS;

        _writeQuorumParamsCheckpoint(params);

        emit MaxQuorumVotesBPSSet(oldMaxQuorumVotesBPS, newMaxQuorumVotesBPS);
    }

Lines in the code

NounsDAOLogicV2.sol#L673-L693 NounsDAOLogicV2.sol#L701-L720

[G22] Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate

Description

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

Lines in the code

The following checkpoints and numCheckpoints variables could be joined in a struct. ERC721Checkpointable.sol#L53 ERC721Checkpointable.sol#L56

[G23] Tight variable packing

Description

Save 1 slot moving the three bool variables (canceled, vetoed and executed) after de address proposer (same for NounsDAOStorageV1, NounsDAOStorageV1Adjusted, NounsDAOStorageV2). Three slots saved in total.

    struct Proposal {
        /// @notice Unique id for looking up a proposal
        uint256 id;
        /// @notice Creator of the proposal
        address proposer;
+       /// @notice Flag marking whether the proposal has been canceled
+       bool canceled;
+       /// @notice Flag marking whether the proposal has been vetoed
+       bool vetoed;
+       /// @notice Flag marking whether the proposal has been executed
+       bool executed;
        /// @notice The number of votes needed to create a proposal at the time of proposal creation. *DIFFERS from GovernerBravo
        uint256 proposalThreshold;
        /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed at the time of proposal creation. *DIFFERS from GovernerBravo
        uint256 quorumVotes;
        /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
        uint256 eta;
        /// @notice the ordered list of target addresses for calls to be made
        address[] targets;
        /// @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made
        uint256[] values;
        /// @notice The ordered list of function signatures to be called
        string[] signatures;
        /// @notice The ordered list of calldata to be passed to each call
        bytes[] calldatas;
        /// @notice The block at which voting begins: holders must delegate their votes prior to this block
        uint256 startBlock;
        /// @notice The block at which voting ends: votes must be cast prior to this block
        uint256 endBlock;
        /// @notice Current number of votes in favor of this proposal
        uint256 forVotes;
        /// @notice Current number of votes in opposition to this proposal
        uint256 againstVotes;
        /// @notice Current number of votes for abstaining for this proposal
        uint256 abstainVotes;
-       /// @notice Flag marking whether the proposal has been canceled
-       bool canceled;
-       /// @notice Flag marking whether the proposal has been vetoed
-       bool vetoed;
-       /// @notice Flag marking whether the proposal has been executed
-       bool executed;
        /// @notice Receipts of ballots for the entire set of voters
        mapping(address => Receipt) receipts;
    }

Lines in the code

NounsDAOInterfaces.sol#L174-L211 NounsDAOInterfaces.sol#L274-L315 NounsDAOInterfaces.sol#L368-L399

[G24] Use bytes32 instead of string

Description

Use bytes32 instead of string when it's possible to save some gas

Lines in the code

NounsDAOLogicV1.sol#L67 NounsDAOLogicV2.sol#L59

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