Medium Severity Risks
Issue | Contexts | |
---|---|---|
MED‑1 | block.number means different things on different L2s |
8 |
MED‑2 | Condition will not revert when block.timestamp is == to the compared variable |
1 |
MED‑3 | Contracts do not work with fee-on-transfer tokens | 13 |
MED‑4 | Some tokens may revert when zero value transfers are made | 4 |
MED‑5 | Use _safeMint instead of _mint |
2 |
MED‑6 | Return values of transfer() /transferFrom() not checked |
7 |
MED‑7 | Use safetransfer Instead Of transfer |
7 |
MED‑8 | Admin privilege - A single point of failure can allow a hacked or malicious owner use critical functions in the project | 16 |
Total: 68 contexts over 10 issues
Low Severity Risks
Issue | Contexts | |
---|---|---|
LOW‑1 | Add to blacklist function |
53 |
LOW‑2 | Array lengths not checked | 5 |
LOW‑3 | Avoid using tx.origin |
2 |
LOW‑4 | Consider the case where totalsupply is 0 |
6 |
LOW‑5 | Use of ecrecover is susceptible to signature malleability |
4 |
LOW‑6 | External call recipient may consume all transaction gas | 6 |
LOW‑7 | External calls in an un-bounded for- loop may result in a DOS |
55 |
LOW‑8 | Init functions are susceptible to front-running | 6 |
LOW‑9 | Possible rounding issue | 16 |
LOW‑10 | Setting Minter to zero address should be avoided | 2 |
LOW‑11 | Missing Contract-existence Checks Before Low-level Calls | 35 |
LOW‑12 | Missing Checks for Address(0x0) | 15 |
LOW‑13 | Contracts are not using their OZ Upgradeable counterparts | 19 |
LOW‑14 | Prevent division by 0 | 4 |
LOW‑15 | Protect your NFT from copying in POW forks | 1 |
LOW‑16 | Contracts are designed to receive ETH but do not implement function for withdrawal | 4 |
LOW‑17 | safeTransfer function does not check for contract existence |
2 |
LOW‑18 | Missing Reentrancy-Guard when using sendValue from OZ's Address.sol |
1 |
LOW‑19 | Solidity version 0.8.20 may not work on other chains due to PUSH0 |
33 |
LOW‑20 | Storage Write Removal Bug On Conditional Early Termination | 1 |
LOW‑21 | TransferOwnership Should Be Two Step | 21 |
LOW‑22 | Unbounded loop | 2 |
LOW‑23 | Unused receive() Function Will Lock Ether In Contract |
4 |
LOW‑24 | No Storage Gap For Upgradeable Contracts | 6 |
LOW‑25 | Upgrade OpenZeppelin Contract Dependency | 4 |
LOW‑26 | Use Ownable2Step rather than Ownable |
4 |
Total: 318 contexts over 30 issues
Issue | Contexts | |
---|---|---|
NC‑1 | address shouldn't be hard-coded |
6 |
NC‑2 | Avoid Floating Pragmas: The Version Should Be Locked | 33 |
NC‑3 | Avoid the use of sensitive terms | 3 |
NC‑4 | No need for == true or == false checks |
4 |
NC‑5 | Variable names that consist of all capital letters should be reserved for constant /immutable variables |
10 |
NC‑6 | Compliance with Solidity Style rules in Constant expressions | 4 |
NC‑7 | Consider adding a deny-list | 3 |
NC‑8 | Consider disabling renounceOwnership() |
2 |
NC‑9 | Consider using named mappings | 29 |
NC‑10 | Consistent usage of require vs custom error | 149 |
NC‑11 | Constant redefined elsewhere | 76 |
NC‑12 | Constants Should Be Defined Rather Than Using Magic Numbers | 3 |
NC‑13 | Constants in comparisons should appear on the left side | 11 |
NC‑14 | Contract does not follow the Solidity style guide's suggested layout ordering | 7 |
NC‑15 | Critical Changes Should Use Two-step Procedure | 14 |
NC‑16 | Duplicated require() /revert() Checks Should Be Refactored To A Modifier Or Function |
6 |
NC‑17 | else -block not required |
5 |
NC‑18 | Event emit should emit a parameter | 3 |
NC‑19 | Mint events missing key information | 2 |
NC‑20 | Events are missing sender information | 63 |
NC‑21 | Event Is Missing Indexed Fields | 61 |
NC‑22 | File is missing NatSpec | 11 |
NC‑23 | Function writing that does not comply with the Solidity Style Guide | 4 |
NC‑24 | Large or complicated code bases should implement fuzzing tests | 1 |
NC‑25 | Use delete to Clear Variables |
32 |
NC‑26 | Immutables can be named using the same convention | 7 |
NC‑27 | Imports can be grouped together | 53 |
NC‑28 | No need to initialize uints to zero | 9 |
NC‑29 | Initial value check is missing in Set Functions | 13 |
NC‑30 | Interfaces should be indicated with an I prefix in the contract name |
1 |
NC‑31 | Lines are too long | 189 |
NC‑32 | Long functions should be refactored into multiple, smaller, functions | 4 |
NC‑33 | Missing event for critical parameter change | 3 |
NC‑34 | NatSpec comments should be increased in contracts | 1 |
NC‑35 | NatSpec @param is missing |
60 |
NC‑36 | NatSpec @return argument is missing |
20 |
NC‑37 | Non-assembly Method Available | 1 |
NC‑38 | Non-external /public function names should begin with an underscore |
74 |
NC‑39 | Non-usage of specific imports | 7 |
NC‑40 | Numeric values having to do with time should use time units for readability | 10 |
NC‑41 | Use a more recent version of Solidity | 33 |
NC‑42 | Omissions in Events | 9 |
NC‑43 | Public Functions Not Called By The Contract Should Be Declared External Instead | 16 |
NC‑44 | Empty blocks should be removed or emit something | 21 |
NC‑45 | Remove forge-std import |
10 |
NC‑46 | Remove unused error definition |
2 |
NC‑47 | Strings should use double quotes rather than single quotes | 26 |
NC‑48 | 2**<N> - 1 Should Be Re-written As type(uint<N>).max |
2 |
NC‑49 | Using underscore at the end of variable name | 41 |
NC‑50 | Large multiples of ten should use scientific notation | 7 |
NC‑51 | Use bytes.concat() |
10 |
NC‑52 | Use @inheritdoc rather than using a non-standard annotation | 12 |
NC‑53 | Use named function calls | 7 |
NC‑54 | Use Underscores for Number Literals | 7 |
NC‑55 | Cast to bytes or bytes32 for clearer semantic meaning |
6 |
NC‑56 | Implement some type of version counter that will be incremented automatically for contract upgrades | 5 |
Total: 56 issues
Gas Optimizations
Issue | Contexts | Estimated Gas Saved | |
---|---|---|---|
GAS‑1 | abi.encode() is less efficient than abi.encodepacked() |
39 | 3900 |
GAS‑2 | Consider activating via-ir for deploying |
1 | 250 |
GAS‑3 | <array>.length Should Not Be Looked Up In Every Loop Of A For-loop |
31 | 3007 |
GAS‑4 | Use assembly to emit events | 132 | 5016 |
GAS‑5 | Comparing to constant boolean | 4 | 32 |
GAS‑6 | Setting the constructor to payable |
12 | 156 |
GAS‑7 | Don't compare boolean expressions to boolean literals | 4 | 36 |
GAS‑8 | Duplicated require() /revert() Checks Should Be Refactored To A Modifier Or Function |
6 | 168 |
GAS‑9 | Empty Blocks Should Be Removed Or Emit Something | 12 | 132 |
GAS‑10 | Using delete statement can save gas |
41 | 328 |
GAS‑11 | ++i Costs Less Gas Than i++ , Especially When It’s Used In For-loops (--i /i-- Too) |
23 | 138 |
GAS‑12 | Use assembly to write address storage values |
6 | 444 |
GAS‑13 | Functions guaranteed to revert when called by normal users can be marked payable |
36 | 756 |
GAS‑14 | It Costs More Gas To Initialize Variables To Zero Than To Let The Default Of Zero Be Applied | 26 | 156 |
GAS‑15 | internal functions only called once can be inlined to save gas |
46 | 1012 |
GAS‑16 | Multiple accesses of a mapping/array should use a local variable cache | 320 | 25600 |
GAS‑17 | Multiple Address Mappings Can Be Combined Into A Single Mapping Of An Address To A Struct, Where Appropriate | 21 | 10500 |
GAS‑18 | Multiplication/division By Two Should Use Bit Shifting | 3 | 24 |
GAS‑19 | Expression `` is cheaper than new bytes(0) |
1 | 259 |
GAS‑20 | Optimize names to save gas | 25 | 550 |
GAS‑21 | <x> += <y> Costs More Gas Than <x> = <x> + <y> For State Variables |
5 | 30 |
GAS‑22 | Structs can be packed into fewer storage slots by editing time variables | 2 | 4000 |
GAS‑23 | Using private rather than public for constants, saves gas |
119 | 404600 |
GAS‑24 | Remove forge-std import |
10 | 1000 |
GAS‑25 | require() /revert() Strings Longer Than 32 Bytes Cost Extra Gas |
70 | 980 |
GAS‑26 | The result of a function call should be cached rather than re-calling the function | 16 | 800 |
GAS‑27 | Shorten the array rather than copying to a new one | 21 | 1050 |
GAS‑28 | Splitting require() Statements That Use && Saves Gas |
2 | 18 |
GAS‑29 | Help The Optimizer By Saving A Storage Variable’s Reference Instead Of Repeatedly Fetching It | 12 | 648 |
GAS‑30 | Structs can be packed into fewer storage slots | 2 | 4000 |
GAS‑31 | Superfluous event fields | 15 | 510 |
GAS‑32 | Usage of uints /ints smaller than 32 bytes (256 bits) incurs overhead |
97 | 582 |
GAS‑33 | ++i /i++ Should Be unchecked{++i} /unchecked{i++} When It Is Not Possible For Them To Overflow, As Is The Case When Used In For- And While-loops |
32 | 1120 |
GAS‑34 | Using unchecked blocks to save gas |
14 | 280 |
GAS‑35 | Use assembly to check for address(0) |
2 | 52 |
GAS‑36 | Use functions instead of modifiers | 33 | 3300 |
GAS‑37 | Use v4.9.0 OpenZeppelin contracts | 4 | 112 |
GAS‑38 | Use of Custom Errors Instead of String | 80 | 960 |
GAS‑39 | Use solidity version 0.8.20 to gain some gas boost | 15 | 1320 |
GAS‑40 | Use Short Circuiting rules to your advantage | 2 | 2000 |
GAS‑41 | Using storage instead of memory saves gas |
3 | 1050 |
GAS‑42 | Use uint256(1) /uint256(2) instead for true and false boolean states |
40 | 200000 |
GAS‑43 | Using Bools For Storage Incurs Overhead | 7 | 119700 |
Total: 43 issues
On Optimism, block.number
is the L2 block number, but on Arbitrum, it's the L1 block number, and ArbSys(address(100)).arbBlockNumber()
must be used. Furthermore, L2 block numbers often occur much more frequently than L1 block numbers (any may even occur on a per-transaction basis), so using block numbers for timing results in inconsistencies, especially when voting is involved across multiple chains. As of version 4.9, OpenZeppelin has modified their governor code to use a clock rather than block numbers, to avoid these sorts of issues, but this still requires that the project implement a clock for each L2.
File: NounsDAOLogicV2.sol
236: temp.startBlock = block.number + votingDelay;
258: newProposal.creationBlock = block.number;
File: NounsDAOLogicV2.sol
1024: uint32 blockNumber = safe32(block.number, 'block number exceeds 32 bits');
File: NounsDAOV3Admin.sol
583: uint32 blockNumber = safe32(block.number, 'block number exceeds 32 bits');
File: NounsDAOV3Proposals.sol
898: uint64 updatePeriodEndBlock = SafeCast.toUint64(block.number + ds.proposalUpdatablePeriodInBlocks);
913: newProposal.creationBlock = SafeCast.toUint64(block.number);
File: NounsDAOLogicV1Fork.sol
313: temp.startBlock = block.number + votingDelay;
335: newProposal.creationBlock = block.number;
The condition does not revert when block.timestamp is equal to the compared >
or <
variable. For example, if there is a check for block.timestamp > proposerSignature.expirationTimestamp
then there should be a check for cases where block.timestamp
is ==
to proposerSignature.expirationTimestamp
File: NounsDAOV3Proposals.sol
989: if (block.timestamp > proposerSignature.expirationTimestamp) revert SignatureExpired();
Some tokens take a transfer fee (e.g. STA, PAXG), some do not currently charge a fee but may do so in the future (e.g. USDT, USDC). Without measuring the balance before and after the transfer, there's no way to ensure that enough tokens were transferred, in the cases where the token has a fee-on-transfer mechanic. If there are latent funds in the contract, subsequent transfers will succeed.
File: NounsDAOExecutorV2.sol
228: IERC20(erc20Token).safeTransfer(recipient, tokensToSend);
File: NounsDAOV3Admin.sol
604: if (erc20tokens[i] == erc20tokens[j]) revert DuplicateTokenAddress();
File: NounsDAOV3Fork.sol
253: IERC20 erc20token = IERC20(ds.erc20TokensToIncludeInFork[i]);
File: NounsAuctionHouseFork.sol
264: IERC20(weth).transfer(to, amount);
File: NounsDAOLogicV1Fork.sol
232: IERC20 erc20token = IERC20(erc20TokensToInclude[i]);
File: NounsDAOLogicV1Fork.sol
798: if (erc20tokens[i] == erc20tokens[j]) revert DuplicateTokenAddress();
File: ERC20Transferer.sol
34: IERC20(token).transferFrom(msg.sender, to, balance);
File: DeployDAOV3NewContractsBase.s.sol
57: (forkEscrow, forkDeployer, daoV3Impl, timelockV2, erc20Transferer) = deployNewContracts();
File: DeployDAOV3NewContractsBase.s.sol
93: erc20Transferer = new ERC20Transferer();
File: ProposeDAOV3UpgradeMainnet.s.sol
29: address erc20Transferer = vm.envAddress('ERC20_TRANSFERER');
33: address[] memory erc20TokensToIncludeInFork = new address[](1);
File: ProposeDAOV3UpgradeTestnet.s.sol
37: address erc20Transferer = vm.envAddress('ERC20_TRANSFERER');
41: address[] memory erc20TokensToIncludeInFork = new address[](1);
- Consider comparing before and after balance to get the actual transferred amount.
- Alternatively, disallow tokens with fee-on-transfer mechanics to be added as reward tokens.
In spite of the fact that EIP-20 states that zero-valued transfers must be accepted, some tokens, such as LEND will revert if this is attempted, which may cause transactions that involve other tokens (such as batch operations) to fully revert. Consider skipping the transfer if the amount is zero, which will also save gas.
File: NounsDAOExecutorV2.sol
221: function sendERC20(
address recipient,
address erc20Token,
uint256 tokensToSend
) external {
require(msg.sender == admin, 'NounsDAOExecutor::sendERC20: Call must come from admin.');
IERC20(erc20Token).safeTransfer(recipient, tokensToSend);
emit ERC20Sent(recipient, erc20Token, tokensToSend);
}
File: NounsDAOForkEscrow.sol
116: function returnTokensToOwner(address owner, uint256[] calldata tokenIds) external onlyDAO {
for (uint256 i = 0; i < tokenIds.length; i++) {
if (currentOwnerOf(tokenIds[i]) != owner) revert NotOwner();
nounsToken.transferFrom(address(this), owner, tokenIds[i]);
escrowedTokensByForkId[forkId][tokenIds[i]] = address(0);
}
numTokensInEscrow -= tokenIds.length;
}
File: NounsDAOForkEscrow.sol
147: function withdrawTokens(uint256[] calldata tokenIds, address to) external onlyDAO {
for (uint256 i = 0; i < tokenIds.length; i++) {
if (currentOwnerOf(tokenIds[i]) != dao) revert NotOwner();
nounsToken.transferFrom(address(this), to, tokenIds[i]);
}
}
File: ERC20Transferer.sol
32: function transferEntireBalance(address token, address to) external returns (uint256) {
uint256 balance = IERC20(token).balanceOf(msg.sender);
IERC20(token).transferFrom(msg.sender, to, balance);
return balance;
}
}
According to openzepplin's ERC721, the use of _mint
is discouraged, use _safeMint whenever possible.
https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#ERC721-_mint-address-uint256-
File: NounsTokenFork.sol
302: _mint(to, nounId);
File: NounsTokenFork.sol
318: _mint(to, nounId);
Use _safeMint
whenever possible instead of _mint
Not all IERC20
implementations revert()
when there's a failure in transfer()
/transferFrom()
. The function signature has a boolean
return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually making a payment
File: NounsDAOForkEscrow.sol
120: nounsToken.transferFrom(address(this), owner, tokenIds[i]);
File: NounsDAOForkEscrow.sol
151: nounsToken.transferFrom(address(this), to, tokenIds[i]);
File: NounsDAOV3Fork.sol
154: ds.nouns.transferFrom(msg.sender, timelock, tokenIds[i]);
File: NounsAuctionHouseFork.sol
248: nouns.transferFrom(address(this), _auction.bidder, _auction.nounId);
File: NounsAuctionHouseFork.sol
264: IERC20(weth).transfer(to, amount);
File: NounsDAOLogicV1Fork.sol
224: nouns.transferFrom(msg.sender, address(timelock), tokenIds[i]);
File: ERC20Transferer.sol
34: IERC20(token).transferFrom(msg.sender, to, balance);
It is good to add a require()
statement that checks the return value of token transfers or to use something like OpenZeppelin’s safeTransfer
/safeTransferFrom
unless one is sure the given token reverts in case of a failure. Failure to do so will cause silent failures of transfers and affect token accounting in contract.
For example, Some tokens do not implement the ERC20 standard properly but are still accepted by most code that accepts ERC20 tokens. For example Tether (USDT)'s transfer() and transferFrom() functions do not return booleans as the specification requires, and instead have no return value. When these sorts of tokens are cast to IERC20, their function signatures do not match and therefore the calls made, revert.
File: NounsDAOForkEscrow.sol
120: nounsToken.transferFrom(address(this), owner, tokenIds[i]);
File: NounsDAOForkEscrow.sol
151: nounsToken.transferFrom(address(this), to, tokenIds[i]);
File: NounsDAOV3Fork.sol
154: ds.nouns.transferFrom(msg.sender, timelock, tokenIds[i]);
File: NounsAuctionHouseFork.sol
248: nouns.transferFrom(address(this), _auction.bidder, _auction.nounId);
File: NounsAuctionHouseFork.sol
264: IERC20(weth).transfer(to, amount);
File: NounsDAOLogicV1Fork.sol
224: nouns.transferFrom(msg.sender, address(timelock), tokenIds[i]);
File: ERC20Transferer.sol
34: IERC20(token).transferFrom(msg.sender, to, balance);
Consider using safeTransfer
/safeTransferFrom
or require()
consistently.
[MED‑8] Admin privilege - A single point of failure can allow a hacked or malicious owner use critical functions in the project
The owner
role has a single point of failure and onlyOwner
can use critical a few functions.
owner
role in the project:
Owner is not behind a multisig and changes are not behind a timelock.
Even if protocol admins/developers are not malicious there is still a chance for Owner keys to be stolen. In such a case, the attacker can cause serious damage to the project due to important functions. In such a case, users who have invested in project will suffer high financial losses.
Hacked owner or malicious owner can immediately use critical functions in the project.
File: NounsAuctionHouseFork.sol
159: function pause() external override onlyOwner {
File: NounsAuctionHouseFork.sol
168: function unpause() external override onlyOwner {
File: NounsAuctionHouseFork.sol
180: function setTimeBuffer(uint256 _timeBuffer) external override onlyOwner {
File: NounsAuctionHouseFork.sol
190: function setReservePrice(uint256 _reservePrice) external override onlyOwner {
File: NounsAuctionHouseFork.sol
200: function setMinBidIncrementPercentage(uint8 _minBidIncrementPercentage) external override onlyOwner {
File: NounsAuctionHouseFork.sol
281: function _authorizeUpgrade(address) internal view override onlyOwner {
File: NounsTokenFork.sol
198: function setContractURIHash(string memory newContractURIHash) external onlyOwner {
File: NounsTokenFork.sol
206: function mint() public override onlyMinter returns (uint256) {
File: NounsTokenFork.sol
213: function burn(uint256 nounId) public override onlyMinter {
File: NounsTokenFork.sol
240: function setMinter(address _minter) external override onlyOwner whenMinterNotLocked {
File: NounsTokenFork.sol
250: function lockMinter() external override onlyOwner whenMinterNotLocked {
File: NounsTokenFork.sol
260: function setDescriptor(INounsDescriptorMinimal _descriptor) external override onlyOwner whenDescriptorNotLocked {
File: NounsTokenFork.sol
270: function lockDescriptor() external override onlyOwner whenDescriptorNotLocked {
File: NounsTokenFork.sol
280: function setSeeder(INounsSeeder _seeder) external override onlyOwner whenSeederNotLocked {
File: NounsTokenFork.sol
290: function lockSeeder() external override onlyOwner whenSeederNotLocked {
File: NounsTokenFork.sol
327: function _authorizeUpgrade(address) internal view override onlyOwner {
Add a time lock to critical functions. Admin-only functions that change critical parameters should emit events and have timelocks. Events allow capturing the changed parameters so that off-chain tools/interfaces can register such changes with timelocks that allow users to evaluate them and consider if they would like to engage/exit based on how they perceive the changes as affecting the trustworthiness of the protocol or profitability of the implemented financial services.
Allow only multi-signature wallets to call the function to reduce the likelihood of an attack.
https://twitter.com/danielvf/status/1572963475101556738?s=20&t=V1kvzfJlsx-D2hfnG0OmuQ
NFT thefts have increased recently, so with the addition of hacked NFTs to the platform, NFTs can be converted into liquidity. To prevent this, I recommend adding the blacklist function.
Marketplaces such as Opensea have a blacklist feature that will not list NFTs that have been reported theft, NFT projects such as Manifold have blacklist functions in their smart contracts.
Here is the project example; Manifold
Manifold Contract https://etherscan.io/address/0xe4e4003afe3765aca8149a82fc064c0b125b9e5a#code
modifier nonBlacklistRequired(address extension) {
require(!_blacklistedExtensions.contains(extension), "Extension blacklisted");
_;
}
Add to Blacklist function and modifier.
If the length of the arrays are not required to be of the same length, user operations may not be fully executed
File: NounsDAOV3Fork.sol
function escrowToFork(
NounsDAOStorageV3.StorageV3 storage ds,
uint256[] calldata tokenIds,
uint256[] calldata proposalIds,
string calldata reason
) external {
File: NounsDAOV3Fork.sol
function joinFork(
NounsDAOStorageV3.StorageV3 storage ds,
uint256[] calldata tokenIds,
uint256[] calldata proposalIds,
string calldata reason
) external {
File: NounsDAOLogicV1Fork.sol
function quit(uint256[] calldata tokenIds, address[] memory erc20TokensToInclude) external nonReentrant {
File: NounsDAOLogicV1Fork.sol
function quitInternal(uint256[] calldata tokenIds, address[] memory erc20TokensToInclude) internal {
tx.origin
is a global variable in Solidity that returns the address of the account that sent the transaction.
Using the variable could make a contract vulnerable if an authorized account calls a malicious contract. You can impersonate a user using a third party contract.
This can make it easier to create a vault on behalf of another user with an external administrator (by receiving it as an argument).
File: NounsDAOLogicV2.sol
1043: (bool refundSent, ) = tx.origin.call{ value: refundAmount }('');
File: NounsDAOLogicV2.sol
1044: emit RefundableVote(tx.origin, refundAmount, refundSent);
Consider the case where totalsupply
is 0. When totalsupply
is 0, it should return 0 directly, because there will be an error of dividing by 0.
This would cause the affected functions to revert and as a result can lead to potential loss.
File: NounsDAOLogicV2.sol
967: uint256 againstVotesBPS = (10000 * againstVotes) / totalSupply;
File: NounsDAOV3DynamicQuorum.sol
63: uint256 againstVotesBPS = (10000 * againstVotes) / totalSupply;
File: NounsDAOV3Fork.sol
247: uint256 ethToSend = (address(timelock).balance * tokenCount) / totalSupply;
254: uint256 tokensToSend = (erc20token.balanceOf(address(timelock)) * tokenCount) / totalSupply;
File: NounsDAOLogicV1Fork.sol
230: uint256 ethToSend = (address(timelock).balance * tokenIds.length) / totalSupply;
233: balancesToSend[i] = (erc20token.balanceOf(address(timelock)) * tokenIds.length) / totalSupply;
Add check for zero value and return 0.
if ( totalSupply() == 0) return 0;
The built-in EVM precompile ecrecover
is susceptible to signature malleability, which could lead to replay attacks.
References: https://swcregistry.io/docs/SWC-117, https://swcregistry.io/docs/SWC-121, and https://medium.com/cryptronics/signature-replay-vulnerabilities-in-smart-contracts-3b6f7596df57.
While this is not immediately exploitable, this may become a vulnerability if used elsewhere.
File: NounsDAOLogicV2.sol
599: function castVoteBySig(
uint256 proposalId,
uint8 support,
uint8 v,
bytes32 r,
bytes32 s
) external {
bytes32 domainSeparator = keccak256(
abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainIdInternal(), address(this))
);
bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
bytes32 digest = keccak256(abi.encodePacked('/x19/x01', domainSeparator, structHash));
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), 'NounsDAO::castVoteBySig: invalid signature');
emit VoteCast(signatory, proposalId, support, castVoteInternal(signatory, proposalId, support), '');
}
File: NounsDAOV3Votes.sol
169: function castVoteBySig(
NounsDAOStorageV3.StorageV3 storage ds,
uint256 proposalId,
uint8 support,
uint8 v,
bytes32 r,
bytes32 s
) external {
bytes32 domainSeparator = keccak256(
abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), block.chainid, address(this))
);
bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
bytes32 digest = keccak256(abi.encodePacked('/x19/x01', domainSeparator, structHash));
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), 'NounsDAO::castVoteBySig: invalid signature');
emit VoteCast(signatory, proposalId, support, castVoteInternal(ds, signatory, proposalId, support), '');
}
File: NounsDAOLogicV1Fork.sol
600: function castVoteBySig(
uint256 proposalId,
uint8 support,
uint8 v,
bytes32 r,
bytes32 s
) external {
bytes32 domainSeparator = keccak256(
abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), block.chainid, address(this))
);
bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
bytes32 digest = keccak256(abi.encodePacked('/x19/x01', domainSeparator, structHash));
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), 'NounsDAO::castVoteBySig: invalid signature');
emit VoteCast(signatory, proposalId, support, castVoteInternal(signatory, proposalId, support), '');
}
File: ERC721CheckpointableUpgradeable.sol
151: function delegateBySig(
address delegatee,
uint256 nonce,
uint256 expiry,
uint8 v,
bytes32 r,
bytes32 s
) public {
require(delegatee != address(0), 'ERC721Checkpointable::delegateBySig: delegatee cannot be zero address');
bytes32 domainSeparator = keccak256(
abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name())), block.chainid, address(this))
);
bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry));
bytes32 digest = keccak256(abi.encodePacked('/x19/x01', domainSeparator, structHash));
address signatory = ecrecover(digest, v, r, s);
require(signatory != address(0), 'ERC721Checkpointable::delegateBySig: invalid signature');
require(nonce == nonces[signatory]++, 'ERC721Checkpointable::delegateBySig: invalid nonce');
require(block.timestamp <= expiry, 'ERC721Checkpointable::delegateBySig: signature expired');
return _delegate(signatory, delegatee);
}
Consider using OpenZeppelin’s ECDSA library (which prevents this malleability) instead of the built-in function. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol#L138-L149
There is no limit specified on the amount of gas used, so the recipient can use up all of the transaction's gas, causing it to revert. Use addr.call{gas: <amount>}("")
or this library instead.
File: NounsDAOExecutor.sol
173: (bool success, bytes memory returnData) = target.call{ value: value }(callData);
File: NounsDAOExecutorV2.sol
196: (bool success, bytes memory returnData) = target.call{ value: value }(callData);
File: NounsDAOLogicV2.sol
824: (bool sent, ) = msg.sender.call{ value: amount }('');
File: NounsDAOLogicV2.sol
1043: (bool refundSent, ) = tx.origin.call{ value: refundAmount }('');
File: NounsDAOV3Admin.sol
470: (bool sent, ) = msg.sender.call{ value: amount }('');
File: NounsDAOV3Votes.sol
305: (bool refundSent, ) = msg.sender.call{ value: refundAmount }('');
Consider limiting the number of iterations in for-
loops that make external calls
File: NounsDAOLogicV2.sol
305: for (uint256 i = 0; i < proposal.targets.length; i++) {
343: for (uint256 i = 0; i < proposal.targets.length; i++) {
372: for (uint256 i = 0; i < proposal.targets.length; i++) {
405: for (uint256 i = 0; i < proposal.targets.length; i++) {
File: NounsDAOV3Proposals.sol
409: for (uint256 i = 0; i < proposerSignatures.length; ++i) {
826: for (uint256 i = 0; i < proposerSignatures.length; ++i) {
File: NounsDAOV3Proposals.sol
446: for (uint256 i = 0; i < proposal.targets.length; i++) {
508: for (uint256 i = 0; i < proposal.targets.length; i++) {
553: for (uint256 i = 0; i < proposal.targets.length; i++) {
602: for (uint256 i = 0; i < proposal.targets.length; i++) {
File: NounsDAOV3Proposals.sol
590: for (uint256 i = 0; i < signers.length; ++i) {
446: for (uint256 i = 0; i < proposal.targets.length; i++) {
508: for (uint256 i = 0; i < proposal.targets.length; i++) {
553: for (uint256 i = 0; i < proposal.targets.length; i++) {
602: for (uint256 i = 0; i < proposal.targets.length; i++) {
File: NounsDAOForkEscrow.sol
117: for (uint256 i = 0; i < tokenIds.length; i++) {
148: for (uint256 i = 0; i < tokenIds.length; i++) {
File: NounsDAOV3Fork.sol
83: for (uint256 i = 0; i < tokenIds.length; i++) {
153: for (uint256 i = 0; i < tokenIds.length; i++) {
File: NounsDAOLogicV1Fork.sol
209: for (uint256 i = 0; i < erc20TokensToInclude.length; i++) {
231: for (uint256 i = 0; i < erc20TokensToInclude.length; i++) {
238: for (uint256 i = 0; i < erc20TokensToInclude.length; i++) {
File: NounsDAOLogicV1Fork.sol
397: for (uint256 i = 0; i < proposal.targets.length; i++) {
435: for (uint256 i = 0; i < proposal.targets.length; i++) {
462: for (uint256 i = 0; i < proposal.targets.length; i++) {
File: NounsTokenFork.sol
149: for (uint256 i = 0; i < tokenIds.length; i++) {
172: for (uint256 i = 0; i < tokenIds.length; i++) {
The initialize()
functions below are not called by another contract atomically after the contract is deployed, so it's possible for a malicious user to call initialize()
which, if it's noticed in time, would require the project to re-deploy the contract in order to properly initialize. Consider creating a factory contract, which will new
and initialize()
each contract atomically.
File: NounsDAOExecutorV2.sol
function initialize(address admin_, uint256 delay_) public virtual initializer {
File: NounsDAOLogicV2.sol
function initialize(
File: NounsDAOLogicV3.sol
function initialize(
File: NounsAuctionHouseFork.sol
function initialize(
File: NounsDAOLogicV1Fork.sol
function initialize(
File: NounsTokenFork.sol
function initialize(
Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator
File: NounsDAOLogicV2.sol
967: uint256 againstVotesBPS = (10000 * againstVotes) / totalSupply;
968: uint256 quorumAdjustmentBPS = (params.quorumCoefficient * againstVotesBPS) / 1e6;
File: NounsDAOLogicV2.sol
1010: uint256 center = upper - (upper - lower) / 2;
File: NounsDAOLogicV2.sol
1067: return (number * bps) / 10000;
File: NounsDAOV3DynamicQuorum.sol
63: uint256 againstVotesBPS = (10000 * againstVotes) / totalSupply;
64: uint256 quorumAdjustmentBPS = (params.quorumCoefficient * againstVotesBPS) / 1e6;
File: NounsDAOV3DynamicQuorum.sol
110: uint256 center = upper - (upper - lower) / 2;
File: NounsDAOV3DynamicQuorum.sol
162: return (number * bps) / 10000;
File: NounsDAOV3Proposals.sol
1016: return (number * bps) / 10000;
File: NounsDAOV3Fork.sol
209: return (adjustedTotalSupply(ds) * ds.forkThresholdBPS) / 10_000;
File: NounsDAOV3Fork.sol
247: uint256 ethToSend = (address(timelock).balance * tokenCount) / totalSupply;
254: uint256 tokensToSend = (erc20token.balanceOf(address(timelock)) * tokenCount) / totalSupply;
File: NounsDAOLogicV1Fork.sol
230: uint256 ethToSend = (address(timelock).balance * tokenIds.length) / totalSupply;
233: balancesToSend[i] = (erc20token.balanceOf(address(timelock)) * tokenIds.length) / totalSupply;
File: NounsDAOLogicV1Fork.sol
786: return (number * bps) / 10000;
File: ERC721CheckpointableUpgradeable.sol
196: uint32 center = upper - (upper - lower) / 2;
The core function mint
is used by users to mint an option position by providing token1 as collateral and borrowing the max amount of liquidity. Address(0)
check is missing in both this function and the internal function _mint
, which is triggered to mint the tokens to the to address. Consider applying a check in the function to ensure tokens aren't minted to the zero address.
File: NounsTokenFork.sol
function setMinter(address _minter) external override onlyOwner whenMinterNotLocked {
minter = _minter;
emit MinterUpdated(_minter);
}
Low-level calls return success if there is no code present at the specified address.
File: NounsDAOExecutor.sol
173: (bool success, bytes memory returnData) = target.call{ value: value }(callData);
File: NounsDAOExecutorV2.sol
196: (bool success, bytes memory returnData) = target.call{ value: value }(callData);
File: NounsDAOLogicV2.sol
248: newProposal.calldatas = calldatas;
File: NounsDAOLogicV2.sol
310: proposal.calldatas[i],
File: NounsDAOLogicV2.sol
348: proposal.calldatas[i],
File: NounsDAOLogicV2.sol
377: proposal.calldatas[i],
File: NounsDAOLogicV2.sol
410: proposal.calldatas[i],
File: NounsDAOLogicV2.sol
437: return (p.targets, p.values, p.signatures, p.calldatas);
File: NounsDAOLogicV2.sol
824: (bool sent, ) = msg.sender.call{ value: amount }('');
File: NounsDAOLogicV2.sol
1043: (bool refundSent, ) = tx.origin.call{ value: refundAmount }('');
File: NounsDAOProxy.sol
95: (bool success, bytes memory returnData) = callee.delegatecall(data);
File: NounsDAOProxy.sol
110: (bool success, ) = implementation.delegatecall(msg.data);
File: NounsDAOV3Admin.sol
470: (bool sent, ) = msg.sender.call{ value: amount }('');
File: NounsDAOV3Proposals.sol
351: proposal.calldatas = calldatas;
File: NounsDAOV3Proposals.sol
420: proposal.calldatas = txs.calldatas;
File: NounsDAOV3Proposals.sol
428: txs.calldatas,
File: NounsDAOV3Proposals.sol
452: proposal.calldatas[i],
File: NounsDAOV3Proposals.sol
513: proposal.calldatas[i],
File: NounsDAOV3Proposals.sol
558: proposal.calldatas[i],
File: NounsDAOV3Proposals.sol
607: proposal.calldatas[i],
File: NounsDAOV3Proposals.sol
688: return (p.targets, p.values, p.signatures, p.calldatas);
File: NounsDAOV3Proposals.sol
864: bytes32[] memory calldatasHashes = new bytes32[](txs.calldatas.length);
File: NounsDAOV3Proposals.sol
865: for (uint256 i = 0; i < txs.calldatas.length; ++i) {
File: NounsDAOV3Proposals.sol
866: calldatasHashes[i] = keccak256(txs.calldatas[i]);
File: NounsDAOV3Proposals.sol
909: newProposal.calldatas = txs.calldatas;
File: NounsDAOV3Proposals.sol
931: txs.calldatas,
File: NounsDAOV3Proposals.sol
970: txs.targets.length != txs.calldatas.length
File: NounsDAOV3Votes.sol
305: (bool refundSent, ) = msg.sender.call{ value: refundAmount }('');
File: NounsAuctionHouseFork.sol
273: (bool success, ) = to.call{ value: value, gas: 30_000 }(new bytes(0));
File: NounsDAOLogicV1Fork.sol
327: newProposal.calldatas = calldatas;
File: NounsDAOLogicV1Fork.sol
402: proposal.calldatas[i],
File: NounsDAOLogicV1Fork.sol
440: proposal.calldatas[i],
File: NounsDAOLogicV1Fork.sol
467: proposal.calldatas[i],
File: NounsDAOLogicV1Fork.sol
494: return (p.targets, p.values, p.signatures, p.calldatas);
In addition to the zero-address checks, add a check to verify that <address>.code.length > 0
Lack of zero-address validation on address parameters may lead to transaction reverts, waste gas, require resubmission of transactions and may even force contract redeployments in certain cases within the protocol.
File: NounsDAOExecutor.sol
97: function setPendingAdmin: address pendingAdmin_
File: NounsDAOExecutor.sol
143: function executeTransaction: address target
File: NounsDAOExecutorV2.sol
120: function setPendingAdmin: address pendingAdmin_
File: NounsDAOExecutorV2.sol
166: function executeTransaction: address target
File: NounsDAOLogicV2.sol
836: function _setPendingAdmin: address newPendingAdmin
File: NounsDAOLogicV2.sol
876: function _setPendingVetoer: address newPendingVetoer
File: NounsDAOV3Admin.sol
261: function _setPendingAdmin: address newPendingAdmin
File: NounsDAOV3Admin.sol
301: function _setPendingVetoer: address newPendingVetoer
File: NounsDAOV3Admin.sol
570: function _setTimelocksAndAdmin: address admin
File: NounsDAOForkEscrow.sol
96: function onERC721Received: address from
File: NounsAuctionHouseFork.sol
79: function initialize: address _weth
File: NounsDAOLogicV1Fork.sol
713: function _setPendingAdmin: address newPendingAdmin
File: NounsTokenFork.sol
121: function initialize: address _minter
File: NounsTokenFork.sol
166: function claimDuringForkPeriod: address to
File: NounsTokenFork.sol
240: function setMinter: address _minter
Consider adding explicit zero-address validation on input parameters of address type.
The non-upgradeable standard version of OpenZeppelin’s library are inherited / used by the contracts. It would be safer to use the upgradeable versions of the library contracts to avoid unexpected behaviour.
File: NounsDAOExecutorProxy.sol
20: import { ERC1967Proxy } from '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol';
File: NounsDAOExecutorV2.sol
42: import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
43: import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
45: import { UUPSUpgradeable } from '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol';
46: import { Address } from '@openzeppelin/contracts/utils/Address.sol';
File: NounsDAOV3Proposals.sol
23: import { SignatureChecker } from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol';
24: import { ECDSA } from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
25: import { SafeCast } from '@openzeppelin/contracts/utils/math/SafeCast.sol';
File: NounsDAOV3Votes.sol
22: import { SafeCast } from '@openzeppelin/contracts/utils/math/SafeCast.sol';
File: ForkDAODeployer.sol
20: import { ERC1967Proxy } from '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol';
File: NounsDAOForkEscrow.sol
21: import { IERC721Receiver } from '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
File: NounsDAOV3Fork.sol
21: import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
File: NounsAuctionHouseFork.sol
35: import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
39: import { UUPSUpgradeable } from '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol';
File: NounsDAOLogicV1Fork.sol
97: import { UUPSUpgradeable } from '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol';
102: import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
File: NounsTokenFork.sol
25: import { IERC721 } from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
26: import { UUPSUpgradeable } from '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol';
File: ERC20Transferer.sol
18: import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
Where applicable, use the contracts from @openzeppelin/contracts-upgradeable
instead of @openzeppelin/contracts
.
See https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/tree/master/contracts for list of available upgradeable contracts
On several locations in the code precautions are not being taken for not dividing by 0, this will revert the code.
These functions can be called with 0 value in the input, this value is not checked for being bigger than 0, that means in some scenarios this can potentially trigger a division by zero.
File: NounsDAOLogicV2.sol
967: uint256 againstVotesBPS = (10000 * againstVotes) / totalSupply
File: NounsDAOV3DynamicQuorum.sol
63: uint256 againstVotesBPS = (10000 * againstVotes) / totalSupply
File: NounsDAOV3Fork.sol
247: uint256 ethToSend = (address(timelock).balance * tokenCount) / totalSupply
File: NounsDAOV3Fork.sol
254: uint256 tokensToSend = (erc20token.balanceOf(address(timelock)) * tokenCount) / totalSupply
Recommend making sure division by 0 won’t occur by checking the variables beforehand and handling this edge case.
Ethereum has performed the long-awaited "merge" that will dramatically reduce the environmental impact of the network
There may be forked versions of Ethereum, which could cause confusion and lead to scams as duplicated NFT assets enter the market.
If the Ethereum Merge, which took place in September 2022, results in the Blockchain splitting into two Blockchains due to the 'THE DAO' attack in 2016, this could result in duplication of immutable tokens (NFTs).
In any case, duplicate NFTs will exist due to the ETH proof-of-work chain and other potential forks, and there’s likely to be some level of confusion around which assets are 'official' or 'authentic.'
Even so, there could be a frenzy for these copies, as NFT owners attempt to flip the proof-of-work versions of their valuable tokens.
As ETHPOW and any other forks spin off of the Ethereum mainnet, they will yield duplicate versions of Ethereum’s NFTs. An NFT is simply a blockchain token, and it can work as a deed of ownership to digital items like artwork and collectibles. A forked Ethereum chain will thus have duplicated deeds that point to the same tokenURI
About Merge Replay Attack: https://twitter.com/elerium115/status/1558471934924431363?s=20&t=RRheaYJwo-GmSnePwofgag
File: NounsTokenFork.sol
222: function tokenURI(
Add the following check:
if(block.chainid != 1) {
revert();
}
The following contracts can receive ETH but can not withdraw, ETH is occasionally sent by users will be stuck in those contracts. This functionality also applies to baseTokens resulting in locked tokens and loss of funds.
File: NounsDAOExecutor.sol
186: receive() external payable {}
File: NounsDAOExecutorV2.sol
209: receive() external payable {}
File: NounsDAOLogicV2.sol
1090: receive() external payable {}
File: NounsDAOProxy.sol
138: receive() external payable {
Provide a rescue ETH and rescueTokens function
According to the implementation of the of the library's safeTransfer function, the contract existences are not checked. It is possible that a token becomes non-existent in the future; for instance, if some bugs are found for the output token, it is possible that the output token will be destroyed through calling its selfdestruct function after migrating its data to another contract for fixing these bugs. When this happens, such output token becomes non-existent but calling the swap function to swap for it does not revert. As a result, the user can lose tokens
File: NounsAuctionHouseFork.sol
261: function _safeTransferETHWithFallback(address to, uint256 amount) internal {
if (!_safeTransferETH(to, amount)) {
IWETH(weth).deposit{ value: amount }();
IERC20(weth).transfer(to, amount);
}
}
File: NounsAuctionHouseFork.sol
272: function _safeTransferETH(address to, uint256 value) internal returns (bool) {
(bool success, ) = to.call{ value: value, gas: 30_000 }(new bytes(0));
return success;
}
In the library's safeTransfer
function, the existence of the contract for token should be checked before performing the low-level call. If such contract does not exist, calling this function should revert.
OZ’s Address.sol library is used. Ether transfer is done with a sendValue
call in the following functions.
There is this warning in OZ’s Address.sol library. Accordingly, he used the Check-Effect-Interaction pattern in the project:
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
It would be best practice to use re-entrancy Guard for reasons such as complicated dangers such as view Re-Entrancy that emerged in the last period and the possibility of expanding the project and its integration with other contracts.
File: NounsDAOExecutorV2.sol
216: recipient.sendValue(ethToSend);
The compiler for Solidity 0.8.20 switches the default target EVM version to Shanghai, which includes the new PUSH0
op code. This op code may not yet be implemented on all L2s, so deployment on these chains will fail. To work around this issue, use an earlier EVM version. While the project itself may or may not compile with 0.8.20, other projects with which it integrates, or which extend this project may, and those projects will have problems deploying these contracts/libraries.
File: NounsDAOExecutor.sol
pragma solidity ^0.8.6;
File: NounsDAOExecutorProxy.sol
pragma solidity ^0.8.19;
File: NounsDAOExecutorV2.sol
pragma solidity ^0.8.19;
File: NounsDAOInterfaces.sol
pragma solidity ^0.8.6;
File: NounsDAOLogicV2.sol
pragma solidity ^0.8.6;
File: NounsDAOLogicV3.sol
pragma solidity ^0.8.19;
File: NounsDAOProxy.sol
pragma solidity ^0.8.6;
File: NounsDAOV3Admin.sol
pragma solidity ^0.8.19;
File: NounsDAOV3DynamicQuorum.sol
pragma solidity ^0.8.19;
File: NounsDAOV3Proposals.sol
pragma solidity ^0.8.19;
File: NounsDAOV3Votes.sol
pragma solidity ^0.8.19;
File: ForkDAODeployer.sol
pragma solidity ^0.8.19;
File: NounsDAOForkEscrow.sol
pragma solidity ^0.8.19;
File: NounsDAOV3Fork.sol
pragma solidity ^0.8.19;
File: NounsAuctionHouseFork.sol
pragma solidity ^0.8.19;
File: INounsTokenForkLike.sol
pragma solidity ^0.8.19;
File: NounsDAOEventsFork.sol
pragma solidity ^0.8.19;
File: NounsDAOLogicV1Fork.sol
pragma solidity ^0.8.19;
File: NounsDAOStorageV1Fork.sol
pragma solidity ^0.8.19;
File: INounsTokenFork.sol
pragma solidity ^0.8.19;
File: NounsTokenFork.sol
pragma solidity ^0.8.19;
File: ERC721CheckpointableUpgradeable.sol
pragma solidity ^0.8.19;
File: ERC20Transferer.sol
pragma solidity ^0.8.16;
File: DeployDAOV3DataContractsBase.s.sol
pragma solidity ^0.8.15;
File: DeployDAOV3DataContractsGoerli.s.sol
pragma solidity ^0.8.15;
File: DeployDAOV3DataContractsSepolia.s.sol
pragma solidity ^0.8.15;
File: DeployDAOV3NewContractsBase.s.sol
pragma solidity ^0.8.15;
File: DeployDAOV3NewContractsMainnet.s.sol
pragma solidity ^0.8.15;
File: DeployDAOV3NewContractsTestnet.s.sol
pragma solidity ^0.8.15;
File: ProposeDAOV3UpgradeMainnet.s.sol
pragma solidity ^0.8.15;
File: ProposeDAOV3UpgradeTestnet.s.sol
pragma solidity ^0.8.15;
File: ProposeENSReverseLookupConfigMainnet.s.sol
pragma solidity ^0.8.15;
File: ProposeTimelockMigrationCleanupMainnet.s.sol
pragma solidity ^0.8.15;
See the following for more info:
https://twitter.com/solidity_lang/status/1567953562151579650?s=20&t=fXIo4hRjOiMXl2dqpD5Oyw
https://blog.soliditylang.org/2022/09/08/storage-write-removal-before-conditional-termination/
File: NounsDAOLogicV2.sol
1070: function getChainIdInternal() internal view returns (uint256) {
uint256 chainId;
assembly {
chainId := chainid()
}
return chainId;
}
Upgrade pragma to at least version 0.8.17
Recommend considering implementing a two step process where the owner or admin nominates an account and the nominated account needs to call an acceptOwnership() function for the transfer of ownership to fully succeed. This ensures the nominated EOA account is a valid and active account.
File: NounsAuctionHouseFork.sol
87: function initialize: _transferOwnership(_owner);
File: NounsTokenFork.sol
129: function initialize: _transferOwnership(_owner);
File: ProposeDAOV3UpgradeMainnet.s.sol
109: function propose: signatures[i] = 'transferOwnership(address)';
File: ProposeDAOV3UpgradeMainnet.s.sol
128: function propose: signatures[i] = 'transferOwnership(address)';
File: ProposeDAOV3UpgradeMainnet.s.sol
135: function propose: signatures[i] = 'transferOwnership(address)';
File: ProposeDAOV3UpgradeTestnet.s.sol
116: function propose: signatures[i] = 'transferOwnership(address)';
File: ProposeTimelockMigrationCleanupMainnet.s.sol
62: function propose: signatures[i] = 'transferOwnership(address)';
File: ProposeTimelockMigrationCleanupMainnet.s.sol
90: function propose: signatures[i] = 'transferOwnership(address)';
File: ProposeTimelockMigrationCleanupMainnet.s.sol
97: function propose: signatures[i] = 'transferOwnership(address)';
Lack of two-step procedure for critical operations leaves them error-prone. Consider adding two step procedure on the critical functions.
New items are pushed into the following arrays but there is no option to pop
them out. Currently, the array can grow indefinitely. E.g. there's no maximum limit and there's no functionality to remove array values.
If the array grows too large, calling relevant functions might run out of gas and revert. Calling these functions could result in a DOS condition.
File: NounsDAOLogicV2.sol
1029: quorumParamsCheckpoints.push(DynamicQuorumParamsCheckpoint({ fromBlock: blockNumber, params: params }));
File: NounsDAOV3Admin.sol
588: ds.quorumParamsCheckpoints.push(
Add a functionality to delete array values or add a maximum size limit for arrays.
If the intention is for the Ether to be used, the function should call another function, otherwise it should revert (e.g. require(msg.sender == address(weth))
). Having no access control on the function means that someone may send Ether to the contract, and have no way to get anything back out, which is a loss of funds. If the concern is having to spend a small amount of gas to check the sender against an immutable address, the code should at least have a function to rescue unused Ether.
File: NounsDAOExecutor.sol
186: receive() external payable {}
File: NounsDAOExecutorV2.sol
209: receive() external payable {}
File: NounsDAOLogicV2.sol
1090: receive() external payable {}
File: NounsDAOLogicV3.sol
1035: receive() external payable {}
For upgradeable contracts, there must be storage gap to "allow developers to freely add new state variables in the future without compromising the storage compatibility with existing deployments". Otherwise it may be very difficult to write new implementation code. Without storage gap, the variable in child contract might be overwritten by the upgraded base contract if new variables are added to the base contract. This could have unintended and very serious consequences to the child contracts.
Refer to the bottom part of this article: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable
However, the contract doesn't contain a storage gap. The storage gap is essential for upgradeable contract because "It allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments". See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps for a description of this storage variable. While some contracts may not currently be sub-classed, adding the variable now protects against forgetting to add it in the future.
File: NounsDAOExecutorV2.sol
File: NounsAuctionHouseFork.sol
File: NounsDAOLogicV1Fork.sol
File: INounsTokenFork.sol
File: NounsTokenFork.sol
File: ERC721CheckpointableUpgradeable.sol
Recommend adding appropriate storage gap at the end of upgradeable contracts such as the below. Please reference OpenZeppelin upgradeable contract templates.
uint256[50] private __gap;
An outdated OZ version is used (which has known vulnerabilities, see: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories). Release: https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.9.0
Require dependencies to be at least version of 4.9.0 to include critical vulnerability patches.
File: package.json
"@openzeppelin/contracts": "^4.1.0"
File: package.json
"@openzeppelin/contracts-upgradeable": "^4.1.0"
File: package.json
"@openzeppelin/contracts": "^4.1.0"
File: package.json
"@openzeppelin/contracts-upgradeable": "^4.1.0"
Update OpenZeppelin Contracts Usage in package.json and require at least version of 4.9.0 via >=4.9.0
Ownable2Step
and Ownable2StepUpgradeable
prevent the contract ownership from mistakenly being transferred to an address that cannot handle it (e.g. due to a typo in the address), by requiring that the recipient of the owner permissions actively accept via a contract call of its own.
File: NounsAuctionHouseFork.sol
34: import { OwnableUpgradeable } from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
45: OwnableUpgradeable,
File: NounsTokenFork.sol
20: import { OwnableUpgradeable } from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
39: contract NounsTokenFork is INounsTokenFork, OwnableUpgradeable, ERC721CheckpointableUpgradeable, UUPSUpgradeable {
It is often better to declare address
es as immutable
, and assign them via constructor arguments. This allows the code to remain the same across deployments on different networks, and avoids recompilation when addresses need to change.
File: ProposeDAOV3UpgradeMainnet.s.sol
11: NounsDAOLogicV1(0x6f3E6272A167e8AcCb32072d08E0957F9c79223d);
File: ProposeDAOV3UpgradeTestnet.s.sol
143: NounsDAOLogicV1(0x9e6D4B42b8Dc567AC4aeCAB369Eb9a3156dF095C);
160: NounsDAOLogicV1(0x35d2670d7C8931AACdd37C89Ddcb0638c3c44A57);
File: ProposeENSReverseLookupConfigMainnet.s.sol
14: NounsDAOLogicV3(payable(0x6f3E6272A167e8AcCb32072d08E0957F9c79223d));
File: ProposeTimelockMigrationCleanupMainnet.s.sol
14: NounsDAOLogicV3(payable(0x6f3E6272A167e8AcCb32072d08E0957F9c79223d));
Avoid floating pragmas for non-library contracts.
While floating pragmas make sense for libraries to allow them to be included with multiple different versions of applications, it may be a security risk for application implementations.
A known vulnerable compiler version may accidentally be selected or security tools might fall-back to an older compiler version ending up checking a different EVM compilation that is ultimately deployed on the blockchain.
It is recommended to pin to a concrete compiler version.
File: NounsDAOExecutor.sol
Found usage of floating pragmas ^0.8.6 of Solidity in [NounsDAOExecutor.sol]
File: NounsDAOExecutorProxy.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOExecutorProxy.sol]
File: NounsDAOExecutorV2.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOExecutorV2.sol]
File: NounsDAOInterfaces.sol
Found usage of floating pragmas ^0.8.6 of Solidity in [NounsDAOInterfaces.sol]
File: NounsDAOLogicV2.sol
Found usage of floating pragmas ^0.8.6 of Solidity in [NounsDAOLogicV2.sol]
File: NounsDAOLogicV3.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOLogicV3.sol]
File: NounsDAOProxy.sol
Found usage of floating pragmas ^0.8.6 of Solidity in [NounsDAOProxy.sol]
File: NounsDAOV3Admin.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOV3Admin.sol]
File: NounsDAOV3DynamicQuorum.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOV3DynamicQuorum.sol]
File: NounsDAOV3Proposals.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOV3Proposals.sol]
File: NounsDAOV3Votes.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOV3Votes.sol]
File: ForkDAODeployer.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [ForkDAODeployer.sol]
File: NounsDAOForkEscrow.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOForkEscrow.sol]
File: NounsDAOV3Fork.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOV3Fork.sol]
File: NounsAuctionHouseFork.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsAuctionHouseFork.sol]
File: INounsTokenForkLike.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [INounsTokenForkLike.sol]
File: NounsDAOEventsFork.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOEventsFork.sol]
File: NounsDAOLogicV1Fork.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOLogicV1Fork.sol]
File: NounsDAOStorageV1Fork.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsDAOStorageV1Fork.sol]
File: INounsTokenFork.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [INounsTokenFork.sol]
File: NounsTokenFork.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [NounsTokenFork.sol]
File: ERC721CheckpointableUpgradeable.sol
Found usage of floating pragmas ^0.8.19 of Solidity in [ERC721CheckpointableUpgradeable.sol]
File: ERC20Transferer.sol
Found usage of floating pragmas ^0.8.16 of Solidity in [ERC20Transferer.sol]
File: DeployDAOV3DataContractsBase.s.sol
Found usage of floating pragmas ^0.8.15 of Solidity in [DeployDAOV3DataContractsBase.s.sol]
File: DeployDAOV3DataContractsGoerli.s.sol
Found usage of floating pragmas ^0.8.15 of Solidity in [DeployDAOV3DataContractsGoerli.s.sol]
File: DeployDAOV3DataContractsSepolia.s.sol
Found usage of floating pragmas ^0.8.15 of Solidity in [DeployDAOV3DataContractsSepolia.s.sol]
File: DeployDAOV3NewContractsBase.s.sol
Found usage of floating pragmas ^0.8.15 of Solidity in [DeployDAOV3NewContractsBase.s.sol]
File: DeployDAOV3NewContractsMainnet.s.sol
Found usage of floating pragmas ^0.8.15 of Solidity in [DeployDAOV3NewContractsMainnet.s.sol]
File: DeployDAOV3NewContractsTestnet.s.sol
Found usage of floating pragmas ^0.8.15 of Solidity in [DeployDAOV3NewContractsTestnet.s.sol]
File: ProposeDAOV3UpgradeMainnet.s.sol
Found usage of floating pragmas ^0.8.15 of Solidity in [ProposeDAOV3UpgradeMainnet.s.sol]
File: ProposeDAOV3UpgradeTestnet.s.sol
Found usage of floating pragmas ^0.8.15 of Solidity in [ProposeDAOV3UpgradeTestnet.s.sol]
File: ProposeENSReverseLookupConfigMainnet.s.sol
Found usage of floating pragmas ^0.8.15 of Solidity in [ProposeENSReverseLookupConfigMainnet.s.sol]
File: ProposeTimelockMigrationCleanupMainnet.s.sol
Found usage of floating pragmas ^0.8.15 of Solidity in [ProposeTimelockMigrationCleanupMainnet.s.sol]
Use alternative variants, e.g. allowlist/denylist instead of whitelist/blacklist
File: NounsDAOLogicV1Fork.sol
108: error TokensMustBeASubsetOfWhitelistedTokens();
211: revert TokensMustBeASubsetOfWhitelistedTokens();
File: NounsTokenFork.sol
35: * - Removed the proxyRegistry feature that whitelisted OpenSea.
There is no need to verify that == true
or == false
when the variable checked upon is a boolean as well.
File: NounsDAOLogicV2.sol
620: require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
File: NounsDAOV3Votes.sol
219: require(receipt.hasVoted == false, 'NounsDAO::castVoteDuringVotingPeriodInternal: voter already voted');
File: NounsDAOV3Votes.sol
282: require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
File: NounsDAOLogicV1Fork.sol
621: require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
Instead simply check for variable
or !variable
[NC‑5] Variable names that consist of all capital letters should be reserved for constant
/immutable
variables
Variable names with all capital letters should be restricted to be used only with constant
or immutable
variables.
If the variable needs to be different based on which class it comes from, a view
/pure
function should be used instead (e.g. like this).
File: NounsDAOInterfaces.sol
391: uint256 public proposalThresholdBPS;
File: NounsDAOInterfaces.sol
394: uint256 public quorumVotesBPS;
File: NounsDAOStorageV1Fork.sol
34: uint256 public proposalThresholdBPS;
File: NounsDAOStorageV1Fork.sol
37: uint256 public quorumVotesBPS;
Variables are declared as constant should utilize the UPPER_CASE_WITH_UNDERSCORES format.
File: NounsDAOLogicV2.sol
59: string public constant name = 'Nouns DAO';
File: NounsDAOV3Votes.sol
44: string public constant name = 'Nouns DAO';
File: NounsDAOLogicV1Fork.sol
116: string public constant name = 'Nouns DAO';
File: ERC721CheckpointableUpgradeable.sol
52: uint8 public constant decimals = 0;
Doing so will significantly increase centralization, but will help to prevent hackers from using stolen tokens
File: NounsDAOForkEscrow.sol
23: contract NounsDAOForkEscrow is IERC721Receiver
File: NounsTokenFork.sol
39: contract NounsTokenFork is INounsTokenFork, OwnableUpgradeable, ERC721CheckpointableUpgradeable, UUPSUpgradeable
File: ERC20Transferer.sol
24: contract ERC20Transferer
Typically, the contract's owner is the account that deploys the contract. As a result, the owner is able to perform certain privileged activities. The OpenZeppelin's Ownable
is used in this project contract implements renounceOwnership
. This can represent a certain risk if the ownership is renounced for any other reason than by design. Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.
File: NounsAuctionHouseFork.sol
45: OwnableUpgradeable,
File: NounsTokenFork.sol
39: contract NounsTokenFork is INounsTokenFork, OwnableUpgradeable, ERC721CheckpointableUpgradeable, UUPSUpgradeable {
Consider moving to solidity version 0.8.18 or later, and using named mappings to make it easier to understand the purpose of each mapping
File: NounsDAOExecutor.sol
70: mapping(bytes32 => bool) public queuedTransactions;
File: NounsDAOExecutorV2.sol
93: mapping(bytes32 => bool) public queuedTransactions;
File: NounsDAOInterfaces.sol
306: mapping(uint256 => Proposal) public proposals;
309: mapping(address => uint256) public latestProposalIds;
409: mapping(address => uint256) public latestProposalIds;
347: mapping(address => Receipt) receipts;
447: mapping(address => Receipt) receipts;
755: mapping(address => Receipt) receipts;
406: mapping(uint256 => Proposal) internal _proposals;
309: mapping(address => uint256) public latestProposalIds;
409: mapping(address => uint256) public latestProposalIds;
347: mapping(address => Receipt) receipts;
447: mapping(address => Receipt) receipts;
755: mapping(address => Receipt) receipts;
679: mapping(uint256 => Proposal) _proposals;
681: mapping(address => uint256) latestProposalIds;
688: mapping(address => mapping(bytes32 => bool)) cancelledSigs;
347: mapping(address => Receipt) receipts;
447: mapping(address => Receipt) receipts;
755: mapping(address => Receipt) receipts;
File: NounsDAOForkEscrow.sol
58: mapping(uint32 => mapping(uint256 => address)) public escrowedTokensByForkId;
File: NounsDAOStorageV1Fork.sol
49: mapping(uint256 => Proposal) public _proposals;
52: mapping(address => uint256) public latestProposalIds;
92: mapping(address => Receipt) receipts;
File: NounsTokenFork.sol
79: mapping(uint256 => INounsSeeder.Seed) public seeds;
File: ERC721CheckpointableUpgradeable.sol
55: mapping(address => address) private _delegates;
64: mapping(address => mapping(uint32 => Checkpoint)) public checkpoints;
67: mapping(address => uint32) public numCheckpoints;
78: mapping(address => uint256) public nonces;
Consider using the same approach throughout the codebase to improve the consistency of the code.
File: NounsDAOExecutor.sol
152: require(queuedTransactions[txHash], "NounsDAOExecutor::executeTransaction: Transaction hasn't been queued.");
File: NounsDAOExecutorV2.sol
175: require(queuedTransactions[txHash], "NounsDAOExecutor::executeTransaction: Transaction hasn't been queued.");
File: NounsDAOLogicV2.sol
146: revert AdminOnly();
646: revert AdminOnly();
664: revert AdminOnly();
683: revert AdminOnly();
704: revert AdminOnly();
734: revert AdminOnly();
761: revert AdminOnly();
789: revert AdminOnly();
820: revert AdminOnly();
361: revert CantCancelExecutedProposal();
391: revert VetoerBurned();
395: revert VetoerOnly();
878: revert VetoerOnly();
399: revert CantVetoExecutedProposal();
146: revert AdminOnly();
646: revert AdminOnly();
664: revert AdminOnly();
683: revert AdminOnly();
704: revert AdminOnly();
734: revert AdminOnly();
761: revert AdminOnly();
789: revert AdminOnly();
820: revert AdminOnly();
146: revert AdminOnly();
646: revert AdminOnly();
664: revert AdminOnly();
683: revert AdminOnly();
704: revert AdminOnly();
734: revert AdminOnly();
761: revert AdminOnly();
789: revert AdminOnly();
820: revert AdminOnly();
146: revert AdminOnly();
646: revert AdminOnly();
664: revert AdminOnly();
683: revert AdminOnly();
704: revert AdminOnly();
734: revert AdminOnly();
761: revert AdminOnly();
789: revert AdminOnly();
820: revert AdminOnly();
146: revert AdminOnly();
646: revert AdminOnly();
664: revert AdminOnly();
683: revert AdminOnly();
704: revert AdminOnly();
734: revert AdminOnly();
761: revert AdminOnly();
789: revert AdminOnly();
820: revert AdminOnly();
146: revert AdminOnly();
646: revert AdminOnly();
664: revert AdminOnly();
683: revert AdminOnly();
704: revert AdminOnly();
734: revert AdminOnly();
761: revert AdminOnly();
789: revert AdminOnly();
820: revert AdminOnly();
146: revert AdminOnly();
646: revert AdminOnly();
664: revert AdminOnly();
683: revert AdminOnly();
704: revert AdminOnly();
734: revert AdminOnly();
761: revert AdminOnly();
789: revert AdminOnly();
820: revert AdminOnly();
146: revert AdminOnly();
646: revert AdminOnly();
664: revert AdminOnly();
683: revert AdminOnly();
704: revert AdminOnly();
734: revert AdminOnly();
761: revert AdminOnly();
789: revert AdminOnly();
820: revert AdminOnly();
795: revert InvalidMinQuorumVotesBPS();
798: revert InvalidMaxQuorumVotesBPS();
801: revert MinQuorumBPSGreaterThanMaxQuorumBPS();
146: revert AdminOnly();
646: revert AdminOnly();
664: revert AdminOnly();
683: revert AdminOnly();
704: revert AdminOnly();
734: revert AdminOnly();
761: revert AdminOnly();
789: revert AdminOnly();
820: revert AdminOnly();
395: revert VetoerOnly();
878: revert VetoerOnly();
888: revert PendingVetoerOnly();
1085: revert UnsafeUint16Cast();
File: NounsDAOV3Admin.sol
152: revert AdminOnly();
217: revert InvalidObjectionPeriodDurationInBlocks();
248: revert InvalidProposalUpdatablePeriodInBlocks();
303: revert VetoerOnly();
316: revert PendingVetoerOnly();
442: revert InvalidMinQuorumVotesBPS();
445: revert InvalidMaxQuorumVotesBPS();
448: revert MinQuorumBPSGreaterThanMaxQuorumBPS();
485: revert VoteSnapshotSwitchAlreadySet();
535: revert ForkPeriodTooLong();
539: revert ForkPeriodTooShort();
604: if (erc20tokens[i] == erc20tokens[j]) revert DuplicateTokenAddress();
File: NounsDAOV3DynamicQuorum.sol
152: revert UnsafeUint16Cast();
File: NounsDAOV3Proposals.sol
226: if (proposerSignatures.length == 0) revert MustProvideSignatures();
394: if (proposerSignatures.length == 0) revert MustProvideSignatures();
251: if (signers.length == 0) revert MustProvideSignatures();
252: if (votes <= propThreshold) revert VotesBelowProposalThreshold();
963: if (votes <= propThreshold) revert VotesBelowProposalThreshold();
226: if (proposerSignatures.length == 0) revert MustProvideSignatures();
394: if (proposerSignatures.length == 0) revert MustProvideSignatures();
398: revert CanOnlyEditUpdatableProposals();
886: revert CanOnlyEditUpdatableProposals();
399: if (msg.sender != proposal.proposer) revert OnlyProposerCanEdit();
887: if (msg.sender != proposal.proposer) revert OnlyProposerCanEdit();
402: if (proposerSignatures.length != signers.length) revert SignerCountMismtach();
414: if (signers[i] != proposerSignatures[i].signer) revert OnlyProposerCanEdit();
504: if (ds.isForkPeriodActive()) revert CannotExecuteDuringForkingPeriod();
538: revert VetoerBurned();
542: revert VetoerOnly();
546: revert CantVetoExecutedProposal();
580: revert CantCancelProposalAtFinalState();
807: ) revert ProposerAlreadyHasALiveProposal();
398: revert CanOnlyEditUpdatableProposals();
886: revert CanOnlyEditUpdatableProposals();
399: if (msg.sender != proposal.proposer) revert OnlyProposerCanEdit();
887: if (msg.sender != proposal.proposer) revert OnlyProposerCanEdit();
888: if (proposal.signers.length > 0) revert ProposerCannotUpdateProposalWithSigners();
252: if (votes <= propThreshold) revert VotesBelowProposalThreshold();
963: if (votes <= propThreshold) revert VotesBelowProposalThreshold();
971: ) revert ProposalInfoArityMismatch();
972: if (txs.targets.length == 0) revert MustProvideActions();
973: if (txs.targets.length > PROPOSAL_MAX_OPERATIONS) revert TooManyActions();
983: if (ds.cancelledSigs[proposerSignature.signer][sigHash]) revert SignatureIsCancelled();
987: revert InvalidSignature();
989: if (block.timestamp > proposerSignature.expirationTimestamp) revert SignatureExpired();
File: NounsDAOV3Votes.sol
195: if (support != 0) revert CanOnlyVoteAgainstDuringObjectionPeriod();
File: NounsDAOLogicV1Fork.sol
211: revert TokensMustBeASubsetOfWhitelistedTokens();
378: revert GovernanceBlockedDuringForkingPeriod();
382: revert WaitingForTokensToClaimOrExpiration();
753: if (msg.sender != admin) revert AdminOnly();
798: if (erc20tokens[i] == erc20tokens[j]) revert DuplicateTokenAddress();
File: NounsTokenFork.sol
151: if (escrow.ownerOfEscrowedToken(forkId, nounId) != msg.sender) revert OnlyTokenOwnerCanClaim();
169: if (msg.sender != escrow.dao()) revert OnlyOriginalDAO();
170: if (block.timestamp >= forkingPeriodEndTimestamp) revert OnlyDuringForkingPeriod();
Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants in a single location is to create an internal constant
in a library
. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync.
File: NounsDAOExecutor.sol
62: uint256 public constant GRACE_PERIOD = 14 days;
63: uint256 public constant MINIMUM_DELAY = 2 days;
64: uint256 public constant MAXIMUM_DELAY = 30 days;
File: NounsDAOExecutorV2.sol
82: string public constant NAME = 'NounsDAOExecutorV2';
85: uint256 public constant GRACE_PERIOD = 21 days;
86: uint256 public constant MINIMUM_DELAY = 2 days;
87: uint256 public constant MAXIMUM_DELAY = 30 days;
File: NounsDAOLogicV2.sol
59: string public constant name = 'Nouns DAO';
62: uint256 public constant MIN_PROPOSAL_THRESHOLD_BPS = 1; // 1 basis point or 0.01%
65: uint256 public constant MAX_PROPOSAL_THRESHOLD_BPS = 1_000; // 1,000 basis points or 10%
68: uint256 public constant MIN_VOTING_PERIOD = 5_760; // About 24 hours
71: uint256 public constant MAX_VOTING_PERIOD = 80_640; // About 2 weeks
74: uint256 public constant MIN_VOTING_DELAY = 1;
77: uint256 public constant MAX_VOTING_DELAY = 40_320; // About 1 week
80: uint256 public constant MIN_QUORUM_VOTES_BPS_LOWER_BOUND = 200; // 200 basis points or 2%
83: uint256 public constant MIN_QUORUM_VOTES_BPS_UPPER_BOUND = 2_000; // 2,000 basis points or 20%
86: uint256 public constant MAX_QUORUM_VOTES_BPS_UPPER_BOUND = 6_000; // 4,000 basis points or 60%
89: uint256 public constant MAX_QUORUM_VOTES_BPS = 2_000; // 2,000 basis points or 20%
92: uint256 public constant proposalMaxOperations = 10; // 10 actions
95: uint256 public constant MAX_REFUND_PRIORITY_FEE = 2 gwei;
98: uint256 public constant REFUND_BASE_GAS = 36000;
101: uint256 public constant MAX_REFUND_GAS_USED = 200_000;
104: uint256 public constant MAX_REFUND_BASE_FEE = 200 gwei;
111: bytes32 public constant BALLOT_TYPEHASH = keccak256('Ballot(uint256 proposalId,uint8 support)');
File: NounsDAOV3Admin.sol
112: uint256 public constant MIN_PROPOSAL_THRESHOLD_BPS = 1; // 1 basis point or 0.01%
115: uint256 public constant MAX_PROPOSAL_THRESHOLD_BPS = 1_000; // 1,000 basis points or 10%
130: uint256 public constant MIN_QUORUM_VOTES_BPS_LOWER_BOUND = 200; // 200 basis points or 2%
133: uint256 public constant MIN_QUORUM_VOTES_BPS_UPPER_BOUND = 2_000; // 2,000 basis points or 20%
136: uint256 public constant MAX_QUORUM_VOTES_BPS_UPPER_BOUND = 6_000; // 6,000 basis points or 60%
File: NounsDAOV3Votes.sol
44: string public constant name = 'Nouns DAO';
51: bytes32 public constant BALLOT_TYPEHASH = keccak256('Ballot(uint256 proposalId,uint8 support)');
54: uint256 public constant MAX_REFUND_PRIORITY_FEE = 2 gwei;
57: uint256 public constant REFUND_BASE_GAS = 36000;
60: uint256 public constant MAX_REFUND_GAS_USED = 200_000;
63: uint256 public constant MAX_REFUND_BASE_FEE = 200 gwei;
File: NounsAuctionHouseFork.sol
48: string public constant NAME = 'NounsAuctionHouseFork';
File: NounsDAOLogicV1Fork.sol
116: string public constant name = 'Nouns DAO';
119: uint256 public constant MIN_PROPOSAL_THRESHOLD_BPS = 1; // 1 basis point or 0.01%
122: uint256 public constant MAX_PROPOSAL_THRESHOLD_BPS = 1_000; // 1,000 basis points or 10%
125: uint256 public constant MIN_VOTING_PERIOD = 7_200; // 24 hours
128: uint256 public constant MAX_VOTING_PERIOD = 100_800; // 2 weeks
131: uint256 public constant MIN_VOTING_DELAY = 1;
134: uint256 public constant MAX_VOTING_DELAY = 100_800; // 2 weeks
140: uint256 public constant MAX_QUORUM_VOTES_BPS = 2_000; // 2,000 basis points or 20%
143: uint256 public constant proposalMaxOperations = 10; // 10 actions
150: bytes32 public constant BALLOT_TYPEHASH = keccak256('Ballot(uint256 proposalId,uint8 support)');
File: NounsTokenFork.sol
46: string public constant NAME = 'NounsTokenFork';
File: DeployDAOV3DataContractsGoerli.s.sol
8: address public constant NOUNS_DAO_PROXY_GOERLI = 0x34b74B5c1996b37e5e3EDB756731A5812FF43F67;
File: DeployDAOV3DataContractsSepolia.s.sol
8: address public constant NOUNS_DAO_PROXY_SEPOLIA = 0x35d2670d7C8931AACdd37C89Ddcb0638c3c44A57;
File: DeployDAOV3NewContractsMainnet.s.sol
9: address public constant NOUNS_TIMELOCK_V1_MAINNET = 0x0BC3807Ec262cB779b38D65b38158acC3bfedE10;
10: uint256 public constant FORK_DAO_VOTING_PERIOD = 36000; // 5 days
11: uint256 public constant FORK_DAO_VOTING_DELAY = 36000; // 5 days
File: DeployDAOV3NewContractsTestnet.s.sol
8: address public constant NOUNS_DAO_PROXY_GOERLI = 0x9e6D4B42b8Dc567AC4aeCAB369Eb9a3156dF095C;
9: address public constant NOUNS_TIMELOCK_V1_GOERLI = 0xADa0F1A73D1df49477fa41C7F8476F9eA5aB115f;
10: uint256 public constant FORK_DAO_VOTING_PERIOD = 40; // 8 minutes
27: uint256 public constant FORK_DAO_VOTING_PERIOD = 40; // 8 minutes
11: uint256 public constant FORK_DAO_VOTING_DELAY = 1;
28: uint256 public constant FORK_DAO_VOTING_DELAY = 1;
25: address public constant NOUNS_DAO_PROXY_SEPOLIA = 0x35d2670d7C8931AACdd37C89Ddcb0638c3c44A57;
26: address public constant NOUNS_TIMELOCK_V1_SEPOLIA = 0x332db58b51393f3a6b28d4DD8964234967e1aD33;
10: uint256 public constant FORK_DAO_VOTING_PERIOD = 40; // 8 minutes
27: uint256 public constant FORK_DAO_VOTING_PERIOD = 40; // 8 minutes
11: uint256 public constant FORK_DAO_VOTING_DELAY = 1;
28: uint256 public constant FORK_DAO_VOTING_DELAY = 1;
File: ProposeDAOV3UpgradeMainnet.s.sol
12: address public constant NOUNS_TIMELOCK_V1_MAINNET = 0x0BC3807Ec262cB779b38D65b38158acC3bfedE10;
14: uint256 public constant ETH_TO_SEND_TO_NEW_TIMELOCK = 10000 ether;
15: uint256 public constant FORK_PERIOD = 7 days;
16: uint256 public constant FORK_THRESHOLD_BPS = 2000;
20: address public constant NOUNS_TOKEN_MAINNET = 0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03;
File: ProposeDAOV3UpgradeTestnet.s.sol
10: uint256 public constant ETH_TO_SEND_TO_NEW_TIMELOCK = 0.001 ether;
11: uint256 public constant FORK_PERIOD = 1 hours;
12: uint256 public constant FORK_THRESHOLD_BPS = 2000;
144: address public constant NOUNS_TIMELOCK_V1_GOERLI = 0xADa0F1A73D1df49477fa41C7F8476F9eA5aB115f;
161: address public constant NOUNS_TIMELOCK_V1_SEPOLIA = 0x332db58b51393f3a6b28d4DD8964234967e1aD33;
File: ProposeTimelockMigrationCleanupMainnet.s.sol
15: address public constant NOUNS_TIMELOCK_V1_MAINNET = 0x0BC3807Ec262cB779b38D65b38158acC3bfedE10;
16: address public constant NOUNS_TOKEN_MAINNET = 0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03;
Even assembly can benefit from using readable constants instead of hex/numeric literals
File: NounsAuctionHouseFork.sol
126: msg.value >= _auction.amount + ((_auction.amount * minBidIncrementPercentage) / 100),
File: ERC721CheckpointableUpgradeable.sol
266: require(n < 2**32, errorMessage);
File: ERC721CheckpointableUpgradeable.sol
271: require(n < 2**96, errorMessage);
Doing so will prevent typo bugs
File: NounsDAOLogicV2.sol
625: if (support == 0) {
File: NounsDAOLogicV2.sol
985: if (len == 0) {
File: NounsDAOLogicV2.sol
1036: if (balance == 0) {
File: NounsDAOV3DynamicQuorum.sol
85: if (len == 0) {
File: NounsDAOV3Proposals.sol
833: if (signerVotes == 0) {
File: NounsDAOV3Votes.sol
225: if (support == 1) {
File: NounsDAOV3Votes.sol
232: if (support == 0) {
File: NounsDAOV3Votes.sol
248: proposal.objectionPeriodEndBlock == 0 &&
File: NounsDAOV3Votes.sol
298: if (balance == 0) {
File: NounsDAOLogicV1Fork.sol
626: if (support == 0) {
File: ERC721CheckpointableUpgradeable.sol
179: if (nCheckpoints == 0) {
The style guide says that, within a contract, the ordering should be 1) Type declarations, 2) State variables, 3) Events, 4) Errors 5) Modifiers, and 6) Functions, but the contract(s) below do not follow this ordering
Various in-scope contracts
The critical procedures should be two step process.
See similar findings in previous Code4rena contests for reference: https://code4rena.com/reports/2022-06-illuminate/#2-critical-changes-should-use-two-step-procedure
File: NounsDAOExecutor.sol
80: function setDelay(uint256 delay_) public {
File: NounsDAOExecutor.sol
97: function setPendingAdmin(address pendingAdmin_) public {
File: NounsDAOExecutorV2.sol
103: function setDelay(uint256 delay_) public {
File: NounsDAOExecutorV2.sol
120: function setPendingAdmin(address pendingAdmin_) public {
File: NounsDAOInterfaces.sol
601: function setApprovalForAll(address operator, bool approved) external;
File: NounsAuctionHouseFork.sol
102: function settleCurrentAndCreateNewAuction() external override nonReentrant whenNotPaused {
File: NounsAuctionHouseFork.sol
111: function settleAuction() external override whenPaused nonReentrant {
File: NounsAuctionHouseFork.sol
180: function setTimeBuffer(uint256 _timeBuffer) external override onlyOwner {
File: NounsAuctionHouseFork.sol
190: function setReservePrice(uint256 _reservePrice) external override onlyOwner {
File: NounsAuctionHouseFork.sol
200: function setMinBidIncrementPercentage(uint8 _minBidIncrementPercentage) external override onlyOwner {
File: NounsTokenFork.sol
198: function setContractURIHash(string memory newContractURIHash) external onlyOwner {
File: NounsTokenFork.sol
240: function setMinter(address _minter) external override onlyOwner whenMinterNotLocked {
File: NounsTokenFork.sol
260: function setDescriptor(INounsDescriptorMinimal _descriptor) external override onlyOwner whenDescriptorNotLocked {
File: NounsTokenFork.sol
280: function setSeeder(INounsSeeder _seeder) external override onlyOwner whenSeederNotLocked {
Lack of two-step procedure for critical operations leaves them error-prone. Consider adding two step procedure on the critical functions.
Saves deployment costs
File: NounsDAOExecutor.sol
74: require(delay_ <= MAXIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must not exceed maximum delay.');
83: require(delay_ <= MAXIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must not exceed maximum delay.');
File: NounsDAOExecutorV2.sol
97: require(delay_ <= MAXIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must not exceed maximum delay.');
106: require(delay_ <= MAXIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must not exceed maximum delay.');
File: NounsTokenFork.sol
223: require(_exists(tokenId), 'NounsToken: URI query for nonexistent token');
232: require(_exists(tokenId), 'NounsToken: URI query for nonexistent token');
One level of nesting can be removed by not having an else
block when the if
-block returns, and if (foo) { return 1; } else { return 2; }
becomes if (foo) { return 1; } return 2;
File: NounsDAOLogicV2.sol
472: } else if (block.timestamp >= proposal.eta + timelock.GRACE_PERIOD()) {
return ProposalState.Expired;
} else {
return ProposalState.Queued;
}
File: NounsDAOV3Proposals.sol
525: if (proposal.executeOnTimelockV1) {
return ds.timelockV1;
} else {
return ds.timelock;
}
File: NounsDAOV3Proposals.sol
662: } else if (block.timestamp >= proposal.eta + getProposalTimelock(ds, proposal).GRACE_PERIOD()) {
return NounsDAOStorageV3.ProposalState.Expired;
} else {
return NounsDAOStorageV3.ProposalState.Queued;
}
File: NounsDAOForkEscrow.sol
187: if (owner == address(0)) {
return dao;
} else {
return owner;
}
File: NounsDAOLogicV1Fork.sol
527: } else if (block.timestamp >= proposal.eta + timelock.GRACE_PERIOD()) {
return ProposalState.Expired;
} else {
return ProposalState.Queued;
}
Some emitted events do not have any emitted parameters. It is recommended to add some parameter such as state changes or value changes when events are emitted
File: NounsTokenFork.sol
253: emit MinterLocked()
File: NounsTokenFork.sol
273: emit DescriptorLocked()
File: NounsTokenFork.sol
293: emit SeederLocked()
Some events are missing key information when emitted. In the following events, they should contain the minted amount and beneficiary to whom it was minted.
File: NounsTokenFork.sol
243: emit MinterUpdated(_minter);
File: NounsTokenFork.sol
253: emit MinterLocked();
The following functions are missing critical parameters when emitting an event.
When dealing with source address which uses the value of msg.sender
, the msg.sender
value must be specified in every transaction, a contract or web page listening to events cannot react to users, emit does not serve the purpose. Basically, this event cannot be used.
File: NounsDAOExecutor.sol
function setDelay(uint256 delay_) public {
require(msg.sender == address(this), 'NounsDAOExecutor::setDelay: Call must come from NounsDAOExecutor.');
require(delay_ >= MINIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must exceed minimum delay.');
require(delay_ <= MAXIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must not exceed maximum delay.');
delay = delay_;
emit NewDelay(delay);
}
File: NounsDAOExecutor.sol
function acceptAdmin() public {
require(msg.sender == pendingAdmin, 'NounsDAOExecutor::acceptAdmin: Call must come from pendingAdmin.');
admin = msg.sender;
pendingAdmin = address(0);
emit NewAdmin(admin);
}
File: NounsDAOExecutor.sol
function setPendingAdmin(address pendingAdmin_) public {
require(
msg.sender == address(this),
'NounsDAOExecutor::setPendingAdmin: Call must come from NounsDAOExecutor.'
);
pendingAdmin = pendingAdmin_;
emit NewPendingAdmin(pendingAdmin);
}
File: NounsDAOExecutor.sol
function queueTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public returns (bytes32) {
require(msg.sender == admin, 'NounsDAOExecutor::queueTransaction: Call must come from admin.');
require(
eta >= getBlockTimestamp() + delay,
'NounsDAOExecutor::queueTransaction: Estimated execution block must satisfy delay.'
);
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = true;
emit QueueTransaction(txHash, target, value, signature, data, eta);
return txHash;
}
File: NounsDAOExecutor.sol
function cancelTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public {
require(msg.sender == admin, 'NounsDAOExecutor::cancelTransaction: Call must come from admin.');
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = false;
emit CancelTransaction(txHash, target, value, signature, data, eta);
}
File: NounsDAOExecutor.sol
function executeTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public returns (bytes memory) {
require(msg.sender == admin, 'NounsDAOExecutor::executeTransaction: Call must come from admin.');
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
require(queuedTransactions[txHash], "NounsDAOExecutor::executeTransaction: Transaction hasn't been queued.");
require(
getBlockTimestamp() >= eta,
"NounsDAOExecutor::executeTransaction: Transaction hasn't surpassed time lock."
);
require(
getBlockTimestamp() <= eta + GRACE_PERIOD,
'NounsDAOExecutor::executeTransaction: Transaction is stale.'
);
queuedTransactions[txHash] = false;
bytes memory callData;
if (bytes(signature).length == 0) {
callData = data;
} else {
callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
}
(bool success, bytes memory returnData) = target.call{ value: value }(callData);
require(success, 'NounsDAOExecutor::executeTransaction: Transaction execution reverted.');
emit ExecuteTransaction(txHash, target, value, signature, data, eta);
return returnData;
}
File: NounsDAOExecutorV2.sol
function setDelay(uint256 delay_) public {
require(msg.sender == address(this), 'NounsDAOExecutor::setDelay: Call must come from NounsDAOExecutor.');
require(delay_ >= MINIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must exceed minimum delay.');
require(delay_ <= MAXIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must not exceed maximum delay.');
delay = delay_;
emit NewDelay(delay_);
}
File: NounsDAOExecutorV2.sol
function setPendingAdmin(address pendingAdmin_) public {
require(
msg.sender == address(this),
'NounsDAOExecutor::setPendingAdmin: Call must come from NounsDAOExecutor.'
);
pendingAdmin = pendingAdmin_;
emit NewPendingAdmin(pendingAdmin_);
}
File: NounsDAOExecutorV2.sol
function queueTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public returns (bytes32) {
require(msg.sender == admin, 'NounsDAOExecutor::queueTransaction: Call must come from admin.');
require(
eta >= getBlockTimestamp() + delay,
'NounsDAOExecutor::queueTransaction: Estimated execution block must satisfy delay.'
);
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = true;
emit QueueTransaction(txHash, target, value, signature, data, eta);
return txHash;
}
File: NounsDAOExecutorV2.sol
function cancelTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public {
require(msg.sender == admin, 'NounsDAOExecutor::cancelTransaction: Call must come from admin.');
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = false;
emit CancelTransaction(txHash, target, value, signature, data, eta);
}
File: NounsDAOExecutorV2.sol
function executeTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public returns (bytes memory) {
require(msg.sender == admin, 'NounsDAOExecutor::executeTransaction: Call must come from admin.');
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
require(queuedTransactions[txHash], "NounsDAOExecutor::executeTransaction: Transaction hasn't been queued.");
require(
getBlockTimestamp() >= eta,
"NounsDAOExecutor::executeTransaction: Transaction hasn't surpassed time lock."
);
require(
getBlockTimestamp() <= eta + GRACE_PERIOD,
'NounsDAOExecutor::executeTransaction: Transaction is stale.'
);
queuedTransactions[txHash] = false;
bytes memory callData;
if (bytes(signature).length == 0) {
callData = data;
} else {
callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
}
(bool success, bytes memory returnData) = target.call{ value: value }(callData);
require(success, 'NounsDAOExecutor::executeTransaction: Transaction execution reverted.');
emit ExecuteTransaction(txHash, target, value, signature, data, eta);
return returnData;
}
File: NounsDAOExecutorV2.sol
function sendETH(address payable recipient, uint256 ethToSend) external {
require(msg.sender == admin, 'NounsDAOExecutor::sendETH: Call must come from admin.');
recipient.sendValue(ethToSend);
emit ETHSent(recipient, ethToSend);
}
File: NounsDAOExecutorV2.sol
function sendERC20(
address recipient,
address erc20Token,
uint256 tokensToSend
) external {
require(msg.sender == admin, 'NounsDAOExecutor::sendERC20: Call must come from admin.');
IERC20(erc20Token).safeTransfer(recipient, tokensToSend);
emit ERC20Sent(recipient, erc20Token, tokensToSend);
}
File: NounsDAOLogicV2.sol
function initialize(
address timelock_,
address nouns_,
address vetoer_,
uint256 votingPeriod_,
uint256 votingDelay_,
uint256 proposalThresholdBPS_,
DynamicQuorumParams calldata dynamicQuorumParams_
) public virtual {
require(address(timelock) == address(0), 'NounsDAO::initialize: can only initialize once');
if (msg.sender != admin) {
revert AdminOnly();
}
require(timelock_ != address(0), 'NounsDAO::initialize: invalid timelock address');
require(nouns_ != address(0), 'NounsDAO::initialize: invalid nouns address');
require(
votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD,
'NounsDAO::initialize: invalid voting period'
);
require(
votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY,
'NounsDAO::initialize: invalid voting delay'
);
require(
proposalThresholdBPS_ >= MIN_PROPOSAL_THRESHOLD_BPS && proposalThresholdBPS_ <= MAX_PROPOSAL_THRESHOLD_BPS,
'NounsDAO::initialize: invalid proposal threshold bps'
);
emit VotingPeriodSet(votingPeriod, votingPeriod_);
emit VotingDelaySet(votingDelay, votingDelay_);
emit ProposalThresholdBPSSet(proposalThresholdBPS, proposalThresholdBPS_);
timelock = INounsDAOExecutor(timelock_);
nouns = NounsTokenLike(nouns_);
vetoer = vetoer_;
votingPeriod = votingPeriod_;
votingDelay = votingDelay_;
proposalThresholdBPS = proposalThresholdBPS_;
_setDynamicQuorumParams(
dynamicQuorumParams_.minQuorumVotesBPS,
dynamicQuorumParams_.maxQuorumVotesBPS,
dynamicQuorumParams_.quorumCoefficient
);
}
struct ProposalTemp {
uint256 totalSupply;
uint256 proposalThreshold;
uint256 latestProposalId;
uint256 startBlock;
uint256 endBlock;
}
File: NounsDAOLogicV2.sol
function cancel(uint256 proposalId) external {
if (state(proposalId) == ProposalState.Executed) {
revert CantCancelExecutedProposal();
}
Proposal storage proposal = _proposals[proposalId];
require(
msg.sender == proposal.proposer ||
nouns.getPriorVotes(proposal.proposer, block.number - 1) <= proposal.proposalThreshold,
'NounsDAO::cancel: proposer above threshold'
);
proposal.canceled = true;
for (uint256 i = 0; i < proposal.targets.length; i++) {
timelock.cancelTransaction(
proposal.targets[i],
proposal.values[i],
proposal.signatures[i],
proposal.calldatas[i],
proposal.eta
);
}
emit ProposalCanceled(proposalId);
}
File: NounsDAOLogicV2.sol
function veto(uint256 proposalId) external {
if (vetoer == address(0)) {
revert VetoerBurned();
}
if (msg.sender != vetoer) {
revert VetoerOnly();
}
if (state(proposalId) == ProposalState.Executed) {
revert CantVetoExecutedProposal();
}
Proposal storage proposal = _proposals[proposalId];
proposal.vetoed = true;
for (uint256 i = 0; i < proposal.targets.length; i++) {
timelock.cancelTransaction(
proposal.targets[i],
proposal.values[i],
proposal.signatures[i],
proposal.calldatas[i],
proposal.eta
);
}
emit ProposalVetoed(proposalId);
}
File: NounsDAOLogicV2.sol
function _setVotingDelay(uint256 newVotingDelay) external {
if (msg.sender != admin) {
revert AdminOnly();
}
require(
newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY,
'NounsDAO::_setVotingDelay: invalid voting delay'
);
uint256 oldVotingDelay = votingDelay;
votingDelay = newVotingDelay;
emit VotingDelaySet(oldVotingDelay, votingDelay);
}
File: NounsDAOLogicV2.sol
function _setVotingPeriod(uint256 newVotingPeriod) external {
if (msg.sender != admin) {
revert AdminOnly();
}
require(
newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD,
'NounsDAO::_setVotingPeriod: invalid voting period'
);
uint256 oldVotingPeriod = votingPeriod;
votingPeriod = newVotingPeriod;
emit VotingPeriodSet(oldVotingPeriod, votingPeriod);
}
File: NounsDAOLogicV2.sol
function _setProposalThresholdBPS(uint256 newProposalThresholdBPS) external {
if (msg.sender != admin) {
revert AdminOnly();
}
require(
newProposalThresholdBPS >= MIN_PROPOSAL_THRESHOLD_BPS &&
newProposalThresholdBPS <= MAX_PROPOSAL_THRESHOLD_BPS,
'NounsDAO::_setProposalThreshold: invalid proposal threshold bps'
);
uint256 oldProposalThresholdBPS = proposalThresholdBPS;
proposalThresholdBPS = newProposalThresholdBPS;
emit ProposalThresholdBPSSet(oldProposalThresholdBPS, proposalThresholdBPS);
}
File: NounsDAOLogicV2.sol
function _setMinQuorumVotesBPS(uint16 newMinQuorumVotesBPS) external {
if (msg.sender != admin) {
revert AdminOnly();
}
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'
);
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);
}
File: NounsDAOLogicV2.sol
function _setMaxQuorumVotesBPS(uint16 newMaxQuorumVotesBPS) external {
if (msg.sender != admin) {
revert AdminOnly();
}
DynamicQuorumParams memory params = getDynamicQuorumParamsAt(block.number);
require(
newMaxQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS_UPPER_BOUND,
'NounsDAO::_setMaxQuorumVotesBPS: invalid max quorum votes bps'
);
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);
}
File: NounsDAOLogicV2.sol
function _setQuorumCoefficient(uint32 newQuorumCoefficient) external {
if (msg.sender != admin) {
revert AdminOnly();
}
DynamicQuorumParams memory params = getDynamicQuorumParamsAt(block.number);
uint32 oldQuorumCoefficient = params.quorumCoefficient;
params.quorumCoefficient = newQuorumCoefficient;
_writeQuorumParamsCheckpoint(params);
emit QuorumCoefficientSet(oldQuorumCoefficient, newQuorumCoefficient);
}
File: NounsDAOLogicV2.sol
function _setDynamicQuorumParams(
uint16 newMinQuorumVotesBPS,
uint16 newMaxQuorumVotesBPS,
uint32 newQuorumCoefficient
) public {
if (msg.sender != admin) {
revert AdminOnly();
}
if (
newMinQuorumVotesBPS < MIN_QUORUM_VOTES_BPS_LOWER_BOUND ||
newMinQuorumVotesBPS > MIN_QUORUM_VOTES_BPS_UPPER_BOUND
) {
revert InvalidMinQuorumVotesBPS();
}
if (newMaxQuorumVotesBPS > MAX_QUORUM_VOTES_BPS_UPPER_BOUND) {
revert InvalidMaxQuorumVotesBPS();
}
if (newMinQuorumVotesBPS > newMaxQuorumVotesBPS) {
revert MinQuorumBPSGreaterThanMaxQuorumBPS();
}
DynamicQuorumParams memory oldParams = getDynamicQuorumParamsAt(block.number);
DynamicQuorumParams memory params = DynamicQuorumParams({
minQuorumVotesBPS: newMinQuorumVotesBPS,
maxQuorumVotesBPS: newMaxQuorumVotesBPS,
quorumCoefficient: newQuorumCoefficient
});
_writeQuorumParamsCheckpoint(params);
emit MinQuorumVotesBPSSet(oldParams.minQuorumVotesBPS, params.minQuorumVotesBPS);
emit MaxQuorumVotesBPSSet(oldParams.maxQuorumVotesBPS, params.maxQuorumVotesBPS);
emit QuorumCoefficientSet(oldParams.quorumCoefficient, params.quorumCoefficient);
}
File: NounsDAOLogicV2.sol
function _withdraw() external returns (uint256, bool) {
if (msg.sender != admin) {
revert AdminOnly();
}
uint256 amount = address(this).balance;
(bool sent, ) = msg.sender.call{ value: amount }('');
emit Withdraw(amount, sent);
return (amount, sent);
}
File: NounsDAOLogicV2.sol
function _setPendingAdmin(address newPendingAdmin) external {
require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only');
address oldPendingAdmin = pendingAdmin;
pendingAdmin = newPendingAdmin;
emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);
}
File: NounsDAOLogicV2.sol
function _acceptAdmin() external {
require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
address oldAdmin = admin;
address oldPendingAdmin = pendingAdmin;
admin = pendingAdmin;
pendingAdmin = address(0);
emit NewAdmin(oldAdmin, admin);
emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);
}
File: NounsDAOLogicV2.sol
function _setPendingVetoer(address newPendingVetoer) public {
if (msg.sender != vetoer) {
revert VetoerOnly();
}
emit NewPendingVetoer(pendingVetoer, newPendingVetoer);
pendingVetoer = newPendingVetoer;
}
File: NounsDAOLogicV2.sol
function _acceptVetoer() external {
if (msg.sender != pendingVetoer) {
revert PendingVetoerOnly();
}
emit NewVetoer(vetoer, pendingVetoer);
vetoer = pendingVetoer;
emit NewPendingVetoer(pendingVetoer, address(0));
pendingVetoer = address(0);
}
File: NounsDAOLogicV2.sol
function _burnVetoPower() public {
require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');
emit NewVetoer(vetoer, address(0));
vetoer = address(0);
emit NewPendingVetoer(pendingVetoer, address(0));
pendingVetoer = address(0);
}
File: NounsDAOProxy.sol
function _setImplementation(address implementation_) public {
require(msg.sender == admin, 'NounsDAOProxy::_setImplementation: admin only');
require(implementation_ != address(0), 'NounsDAOProxy::_setImplementation: invalid implementation address');
address oldImplementation = implementation;
implementation = implementation_;
emit NewImplementation(oldImplementation, implementation);
}
File: NounsDAOV3Admin.sol
function _acceptAdmin(NounsDAOStorageV3.StorageV3 storage ds) external {
require(
msg.sender == ds.pendingAdmin && msg.sender != address(0),
'NounsDAO::_acceptAdmin: pending admin only'
);
address oldAdmin = ds.admin;
address oldPendingAdmin = ds.pendingAdmin;
ds.admin = ds.pendingAdmin;
ds.pendingAdmin = address(0);
emit NewAdmin(oldAdmin, ds.admin);
emit NewPendingAdmin(oldPendingAdmin, address(0));
}
File: NounsDAOV3Admin.sol
function _setPendingVetoer(NounsDAOStorageV3.StorageV3 storage ds, address newPendingVetoer) public {
if (msg.sender != ds.vetoer) {
revert VetoerOnly();
}
emit NewPendingVetoer(ds.pendingVetoer, newPendingVetoer);
ds.pendingVetoer = newPendingVetoer;
}
File: NounsDAOV3Admin.sol
function _acceptVetoer(NounsDAOStorageV3.StorageV3 storage ds) external {
if (msg.sender != ds.pendingVetoer) {
revert PendingVetoerOnly();
}
emit NewVetoer(ds.vetoer, ds.pendingVetoer);
ds.vetoer = ds.pendingVetoer;
emit NewPendingVetoer(ds.pendingVetoer, address(0));
ds.pendingVetoer = address(0);
}
File: NounsDAOV3Admin.sol
function _burnVetoPower(NounsDAOStorageV3.StorageV3 storage ds) public {
require(msg.sender == ds.vetoer, 'NounsDAO::_burnVetoPower: vetoer only');
emit NewVetoer(ds.vetoer, address(0));
ds.vetoer = address(0);
emit NewPendingVetoer(ds.pendingVetoer, address(0));
ds.pendingVetoer = address(0);
}
File: NounsDAOV3Admin.sol
function _withdraw(NounsDAOStorageV3.StorageV3 storage ds) external onlyAdmin(ds) returns (uint256, bool) {
uint256 amount = address(this).balance;
(bool sent, ) = msg.sender.call{ value: amount }('');
emit Withdraw(amount, sent);
return (amount, sent);
}
File: NounsDAOV3Proposals.sol
function propose(
NounsDAOStorageV3.StorageV3 storage ds,
ProposalTxs memory txs,
string memory description
) internal returns (uint256) {
uint256 adjustedTotalSupply = ds.adjustedTotalSupply();
uint256 proposalThreshold_ = checkPropThreshold(
ds,
ds.nouns.getPriorVotes(msg.sender, block.number - 1),
adjustedTotalSupply
);
checkProposalTxs(txs);
checkNoActiveProp(ds, msg.sender);
uint256 proposalId = ds.proposalCount = ds.proposalCount + 1;
NounsDAOStorageV3.Proposal storage newProposal = createNewProposal(
ds,
proposalId,
proposalThreshold_,
adjustedTotalSupply,
txs
);
ds.latestProposalIds[msg.sender] = proposalId;
emitNewPropEvents(newProposal, new address[](0), ds.minQuorumVotes(adjustedTotalSupply), txs, description);
return proposalId;
}
File: NounsDAOV3Proposals.sol
function veto(NounsDAOStorageV3.StorageV3 storage ds, uint256 proposalId) external {
if (ds.vetoer == address(0)) {
revert VetoerBurned();
}
if (msg.sender != ds.vetoer) {
revert VetoerOnly();
}
if (stateInternal(ds, proposalId) == NounsDAOStorageV3.ProposalState.Executed) {
revert CantVetoExecutedProposal();
}
NounsDAOStorageV3.Proposal storage proposal = ds._proposals[proposalId];
proposal.vetoed = true;
INounsDAOExecutor timelock = getProposalTimelock(ds, proposal);
for (uint256 i = 0; i < proposal.targets.length; i++) {
timelock.cancelTransaction(
proposal.targets[i],
proposal.values[i],
proposal.signatures[i],
proposal.calldatas[i],
proposal.eta
);
}
emit ProposalVetoed(proposalId);
}
File: NounsDAOV3Proposals.sol
function cancel(NounsDAOStorageV3.StorageV3 storage ds, uint256 proposalId) external {
NounsDAOStorageV3.ProposalState proposalState = stateInternal(ds, proposalId);
if (
proposalState == NounsDAOStorageV3.ProposalState.Canceled ||
proposalState == NounsDAOStorageV3.ProposalState.Defeated ||
proposalState == NounsDAOStorageV3.ProposalState.Expired ||
proposalState == NounsDAOStorageV3.ProposalState.Executed ||
proposalState == NounsDAOStorageV3.ProposalState.Vetoed
) {
revert CantCancelProposalAtFinalState();
}
NounsDAOStorageV3.Proposal storage proposal = ds._proposals[proposalId];
address proposer = proposal.proposer;
NounsTokenLike nouns = ds.nouns;
uint256 votes = nouns.getPriorVotes(proposer, block.number - 1);
bool msgSenderIsProposer = proposer == msg.sender;
address[] memory signers = proposal.signers;
for (uint256 i = 0; i < signers.length; ++i) {
msgSenderIsProposer = msgSenderIsProposer || msg.sender == signers[i];
votes += nouns.getPriorVotes(signers[i], block.number - 1);
}
require(
msgSenderIsProposer || votes <= proposal.proposalThreshold,
'NounsDAO::cancel: proposer above threshold'
);
proposal.canceled = true;
INounsDAOExecutor timelock = getProposalTimelock(ds, proposal);
for (uint256 i = 0; i < proposal.targets.length; i++) {
timelock.cancelTransaction(
proposal.targets[i],
proposal.values[i],
proposal.signatures[i],
proposal.calldatas[i],
proposal.eta
);
}
emit ProposalCanceled(proposalId);
}
File: NounsDAOV3Fork.sol
function withdrawDAONounsFromEscrow(
NounsDAOStorageV3.StorageV3 storage ds,
uint256[] calldata tokenIds,
address to
) private {
if (msg.sender != ds.admin) {
revert AdminOnly();
}
ds.forkEscrow.withdrawTokens(tokenIds, to);
emit DAOWithdrawNounsFromEscrow(tokenIds, to);
}
File: NounsAuctionHouseFork.sol
function createBid(uint256 nounId) external payable override nonReentrant {
INounsAuctionHouse.Auction memory _auction = auction;
require(_auction.nounId == nounId, 'Noun not up for auction');
require(block.timestamp < _auction.endTime, 'Auction expired');
require(msg.value >= reservePrice, 'Must send at least reservePrice');
require(
msg.value >= _auction.amount + ((_auction.amount * minBidIncrementPercentage) / 100),
'Must send more than last bid by minBidIncrementPercentage amount'
);
address payable lastBidder = _auction.bidder;
if (lastBidder != address(0)) {
_safeTransferETHWithFallback(lastBidder, _auction.amount);
}
auction.amount = msg.value;
auction.bidder = payable(msg.sender);
bool extended = _auction.endTime - block.timestamp < timeBuffer;
if (extended) {
auction.endTime = _auction.endTime = block.timestamp + timeBuffer;
}
emit AuctionBid(_auction.nounId, msg.sender, msg.value, extended);
if (extended) {
emit AuctionExtended(_auction.nounId, _auction.endTime);
}
}
File: NounsDAOLogicV1Fork.sol
function cancel(uint256 proposalId) external {
require(state(proposalId) != ProposalState.Executed, 'NounsDAO::cancel: cannot cancel executed proposal');
Proposal storage proposal = _proposals[proposalId];
require(
msg.sender == proposal.proposer ||
nouns.getPriorVotes(proposal.proposer, block.number - 1) <= proposal.proposalThreshold,
'NounsDAO::cancel: proposer above threshold'
);
proposal.canceled = true;
for (uint256 i = 0; i < proposal.targets.length; i++) {
timelock.cancelTransaction(
proposal.targets[i],
proposal.values[i],
proposal.signatures[i],
proposal.calldatas[i],
proposal.eta
);
}
emit ProposalCanceled(proposalId);
}
File: NounsDAOLogicV1Fork.sol
function _setVotingDelay(uint256 newVotingDelay) external {
require(msg.sender == admin, 'NounsDAO::_setVotingDelay: admin only');
require(
newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY,
'NounsDAO::_setVotingDelay: invalid voting delay'
);
uint256 oldVotingDelay = votingDelay;
votingDelay = newVotingDelay;
emit VotingDelaySet(oldVotingDelay, newVotingDelay);
}
File: NounsDAOLogicV1Fork.sol
function _setVotingPeriod(uint256 newVotingPeriod) external {
require(msg.sender == admin, 'NounsDAO::_setVotingPeriod: admin only');
require(
newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD,
'NounsDAO::_setVotingPeriod: invalid voting period'
);
uint256 oldVotingPeriod = votingPeriod;
votingPeriod = newVotingPeriod;
emit VotingPeriodSet(oldVotingPeriod, newVotingPeriod);
}
File: NounsDAOLogicV1Fork.sol
function _setProposalThresholdBPS(uint256 newProposalThresholdBPS) external {
require(msg.sender == admin, 'NounsDAO::_setProposalThresholdBPS: admin only');
require(
newProposalThresholdBPS >= MIN_PROPOSAL_THRESHOLD_BPS &&
newProposalThresholdBPS <= MAX_PROPOSAL_THRESHOLD_BPS,
'NounsDAO::_setProposalThreshold: invalid proposal threshold'
);
uint256 oldProposalThresholdBPS = proposalThresholdBPS;
proposalThresholdBPS = newProposalThresholdBPS;
emit ProposalThresholdBPSSet(oldProposalThresholdBPS, newProposalThresholdBPS);
}
File: NounsDAOLogicV1Fork.sol
function _setQuorumVotesBPS(uint256 newQuorumVotesBPS) external {
require(msg.sender == admin, 'NounsDAO::_setQuorumVotesBPS: admin only');
require(
newQuorumVotesBPS >= MIN_QUORUM_VOTES_BPS && newQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS,
'NounsDAO::_setQuorumVotesBPS: invalid quorum votes basis points'
);
uint256 oldQuorumVotesBPS = quorumVotesBPS;
quorumVotesBPS = newQuorumVotesBPS;
emit QuorumVotesBPSSet(oldQuorumVotesBPS, newQuorumVotesBPS);
}
File: NounsDAOLogicV1Fork.sol
function _setPendingAdmin(address newPendingAdmin) external {
require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only');
address oldPendingAdmin = pendingAdmin;
pendingAdmin = newPendingAdmin;
emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);
}
File: NounsDAOLogicV1Fork.sol
function _acceptAdmin() external {
require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
address oldAdmin = admin;
address oldPendingAdmin = pendingAdmin;
admin = pendingAdmin;
pendingAdmin = address(0);
emit NewAdmin(oldAdmin, admin);
emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);
}
File: NounsDAOLogicV1Fork.sol
function _setErc20TokensToIncludeInQuit(address[] calldata erc20tokens) external {
if (msg.sender != admin) revert AdminOnly();
checkForDuplicates(erc20tokens);
emit ERC20TokensToIncludeInQuitSet(erc20TokensToIncludeInQuit, erc20tokens);
erc20TokensToIncludeInQuit = erc20tokens;
}
Add msg.sender
parameter in event-emit
Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields).
Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.
File: NounsDAOInterfaces.sol
event ProposalCreated(
uint256 id,
address proposer,
address[] targets,
uint256[] values,
string[] signatures,
bytes[] calldatas,
uint256 startBlock,
uint256 endBlock,
string description
);
File: NounsDAOInterfaces.sol
event ProposalCreatedWithRequirements(
uint256 id,
address proposer,
address[] targets,
uint256[] values,
string[] signatures,
bytes[] calldatas,
uint256 startBlock,
uint256 endBlock,
uint256 proposalThreshold,
uint256 quorumVotes,
string description
);
File: NounsDAOInterfaces.sol
event ProposalQueued(uint256 id, uint256 eta);
File: NounsDAOInterfaces.sol
event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay);
File: NounsDAOInterfaces.sol
event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);
File: NounsDAOInterfaces.sol
event NewImplementation(address oldImplementation, address newImplementation);
File: NounsDAOInterfaces.sol
event ProposalThresholdBPSSet(uint256 oldProposalThresholdBPS, uint256 newProposalThresholdBPS);
File: NounsDAOInterfaces.sol
event QuorumVotesBPSSet(uint256 oldQuorumVotesBPS, uint256 newQuorumVotesBPS);
File: NounsDAOInterfaces.sol
event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);
File: NounsDAOInterfaces.sol
event NewAdmin(address oldAdmin, address newAdmin);
File: NounsDAOInterfaces.sol
event NewVetoer(address oldVetoer, address newVetoer);
File: NounsDAOInterfaces.sol
event MinQuorumVotesBPSSet(uint16 oldMinQuorumVotesBPS, uint16 newMinQuorumVotesBPS);
File: NounsDAOInterfaces.sol
event MaxQuorumVotesBPSSet(uint16 oldMaxQuorumVotesBPS, uint16 newMaxQuorumVotesBPS);
File: NounsDAOInterfaces.sol
event QuorumCoefficientSet(uint32 oldQuorumCoefficient, uint32 newQuorumCoefficient);
File: NounsDAOInterfaces.sol
event Withdraw(uint256 amount, bool sent);
File: NounsDAOInterfaces.sol
event NewPendingVetoer(address oldPendingVetoer, address newPendingVetoer);
File: NounsDAOInterfaces.sol
event ObjectionPeriodDurationSet(
uint32 oldObjectionPeriodDurationInBlocks,
uint32 newObjectionPeriodDurationInBlocks
);
File: NounsDAOInterfaces.sol
event LastMinuteWindowSet(uint32 oldLastMinuteWindowInBlocks, uint32 newLastMinuteWindowInBlocks);
File: NounsDAOInterfaces.sol
event ProposalUpdatablePeriodSet(
uint32 oldProposalUpdatablePeriodInBlocks,
uint32 newProposalUpdatablePeriodInBlocks
);
File: NounsDAOInterfaces.sol
event VoteSnapshotBlockSwitchProposalIdSet(
uint256 oldVoteSnapshotBlockSwitchProposalId,
uint256 newVoteSnapshotBlockSwitchProposalId
);
File: NounsDAOInterfaces.sol
event ERC20TokensToIncludeInForkSet(address[] oldErc20Tokens, address[] newErc20tokens);
File: NounsDAOInterfaces.sol
event ForkDAODeployerSet(address oldForkDAODeployer, address newForkDAODeployer);
File: NounsDAOInterfaces.sol
event ForkPeriodSet(uint256 oldForkPeriod, uint256 newForkPeriod);
File: NounsDAOInterfaces.sol
event ForkThresholdSet(uint256 oldForkThreshold, uint256 newForkThreshold);
File: NounsDAOInterfaces.sol
event TimelocksAndAdminSet(address timelock, address timelockV1, address admin);
File: NounsDAOInterfaces.sol
event DAOWithdrawNounsFromEscrow(uint256[] tokenIds, address to);
File: NounsDAOInterfaces.sol
event DAONounsSupplyIncreasedFromEscrow(uint256 numTokens, address to);
File: NounsDAOV3Admin.sol
event ProposalThresholdBPSSet(uint256 oldProposalThresholdBPS, uint256 newProposalThresholdBPS);
File: NounsDAOV3Admin.sol
event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay);
File: NounsDAOV3Admin.sol
event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);
File: NounsDAOV3Admin.sol
event ObjectionPeriodDurationSet(
uint32 oldObjectionPeriodDurationInBlocks,
uint32 newObjectionPeriodDurationInBlocks
);
File: NounsDAOV3Admin.sol
event LastMinuteWindowSet(uint32 oldLastMinuteWindowInBlocks, uint32 newLastMinuteWindowInBlocks);
File: NounsDAOV3Admin.sol
event ProposalUpdatablePeriodSet(
uint32 oldProposalUpdatablePeriodInBlocks,
uint32 newProposalUpdatablePeriodInBlocks
);
File: NounsDAOV3Admin.sol
event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);
File: NounsDAOV3Admin.sol
event NewAdmin(address oldAdmin, address newAdmin);
File: NounsDAOV3Admin.sol
event NewPendingVetoer(address oldPendingVetoer, address newPendingVetoer);
File: NounsDAOV3Admin.sol
event NewVetoer(address oldVetoer, address newVetoer);
File: NounsDAOV3Admin.sol
event MinQuorumVotesBPSSet(uint16 oldMinQuorumVotesBPS, uint16 newMinQuorumVotesBPS);
File: NounsDAOV3Admin.sol
event MaxQuorumVotesBPSSet(uint16 oldMaxQuorumVotesBPS, uint16 newMaxQuorumVotesBPS);
File: NounsDAOV3Admin.sol
event QuorumCoefficientSet(uint32 oldQuorumCoefficient, uint32 newQuorumCoefficient);
File: NounsDAOV3Admin.sol
event Withdraw(uint256 amount, bool sent);
File: NounsDAOV3Admin.sol
event VoteSnapshotBlockSwitchProposalIdSet(
uint256 oldVoteSnapshotBlockSwitchProposalId,
uint256 newVoteSnapshotBlockSwitchProposalId
);
File: NounsDAOV3Admin.sol
event ForkDAODeployerSet(address oldForkDAODeployer, address newForkDAODeployer);
File: NounsDAOV3Admin.sol
event ERC20TokensToIncludeInForkSet(address[] oldErc20Tokens, address[] newErc20tokens);
File: NounsDAOV3Admin.sol
event ForkEscrowSet(address oldForkEscrow, address newForkEscrow);
File: NounsDAOV3Admin.sol
event ForkPeriodSet(uint256 oldForkPeriod, uint256 newForkPeriod);
File: NounsDAOV3Admin.sol
event ForkThresholdSet(uint256 oldForkThreshold, uint256 newForkThreshold);
File: NounsDAOV3Admin.sol
event TimelocksAndAdminSet(address timelock, address timelockV1, address admin);
File: NounsDAOV3Proposals.sol
event ProposalCreated(
uint256 id,
address proposer,
address[] targets,
uint256[] values,
string[] signatures,
bytes[] calldatas,
uint256 startBlock,
uint256 endBlock,
string description
);
File: NounsDAOV3Proposals.sol
event ProposalCreatedWithRequirements(
uint256 id,
address proposer,
address[] signers,
address[] targets,
uint256[] values,
string[] signatures,
bytes[] calldatas,
uint256 startBlock,
uint256 endBlock,
uint256 updatePeriodEndBlock,
uint256 proposalThreshold,
uint256 quorumVotes,
string description
);
File: NounsDAOV3Proposals.sol
event ProposalQueued(uint256 id, uint256 eta);
File: ForkDAODeployer.sol
event DAODeployed(address token, address auction, address governor, address treasury);
File: NounsDAOV3Fork.sol
event DAOWithdrawNounsFromEscrow(uint256[] tokenIds, address to);
File: NounsDAOV3Fork.sol
event DAONounsSupplyIncreasedFromEscrow(uint256 numTokens, address to);
File: NounsDAOEventsFork.sol
event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay);
File: NounsDAOEventsFork.sol
event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);
File: NounsDAOEventsFork.sol
event NewImplementation(address oldImplementation, address newImplementation);
File: NounsDAOEventsFork.sol
event ProposalThresholdBPSSet(uint256 oldProposalThresholdBPS, uint256 newProposalThresholdBPS);
File: NounsDAOEventsFork.sol
event QuorumVotesBPSSet(uint256 oldQuorumVotesBPS, uint256 newQuorumVotesBPS);
File: NounsDAOLogicV1Fork.sol
event ERC20TokensToIncludeInQuitSet(address[] oldErc20Tokens, address[] newErc20tokens);
Various in-scope contract files.
Order of Functions; ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier. But there are contracts in the project that do not comply with this.
https://docs.soliditylang.org/en/v0.8.17/style-guide.html
Functions should be grouped according to their visibility and ordered:
- constructor
- receive function (if exists)
- fallback function (if exists)
- external
- public
- internal
- private
- within a grouping, place the view and pure functions last
Various in-scope contract files.
Large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts, should implement fuzzing tests. Fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and fuzzers, with properly and extensively-written invariants, can close this testing gap significantly.
Various in-scope contract files.
delete a
assigns the initial value for the type to a
. i.e. for integers it is equivalent to a = 0
, but it can also be used on arrays, where it assigns a dynamic array of length zero or a static array of the same length with all elements reset. For structs, it assigns a struct with all members reset. Similarly, it can also be used to set an address to zero address. It has no effect on whole mappings though (as the keys of mappings may be arbitrary and are generally unknown). However, individual keys and what they map to can be deleted: If a
is a mapping, then delete a[x]
will delete the value stored at x
.
The delete
key better conveys the intention and is also more idiomatic. Consider replacing assignments of zero with delete
statements.
File: NounsDAOLogicV2.sol
244: newProposal.eta = 0;
251: newProposal.forVotes = 0;
252: newProposal.againstVotes = 0;
253: newProposal.abstainVotes = 0;
File: NounsDAOV3Votes.sol
289: receipt.support = 0;
File: NounsDAOForkEscrow.sol
134: numTokensInEscrow = 0;
File: NounsDAOLogicV1Fork.sol
323: newProposal.eta = 0;
330: newProposal.forVotes = 0;
331: newProposal.againstVotes = 0;
332: newProposal.abstainVotes = 0;
File: ProposeDAOV3UpgradeMainnet.s.sol
84: values[i] = 0;
84: values[i] = 0;
84: values[i] = 0;
84: values[i] = 0;
84: values[i] = 0;
84: values[i] = 0;
84: values[i] = 0;
84: values[i] = 0;
84: values[i] = 0;
File: ProposeDAOV3UpgradeTestnet.s.sol
91: values[i] = 0;
91: values[i] = 0;
91: values[i] = 0;
91: values[i] = 0;
91: values[i] = 0;
91: values[i] = 0;
91: values[i] = 0;
File: ProposeENSReverseLookupConfigMainnet.s.sol
42: values[i] = 0;
File: ProposeTimelockMigrationCleanupMainnet.s.sol
61: values[i] = 0;
61: values[i] = 0;
61: values[i] = 0;
61: values[i] = 0;
61: values[i] = 0;
Immutables should be in uppercase per naming convention. As shown below, some immutables are named using capital letters and underscores while some are not. For a better code quality, please consider naming these immutables using the same naming convention.
File: NounsDAOForkEscrow.sol
41: address public immutable dao;
44: NounsTokenLike public immutable nounsToken;
File: DeployDAOV3NewContractsBase.s.sol
20: uint256 public immutable forkDAOVotingPeriod;
21: uint256 public immutable forkDAOVotingDelay;
25: NounsDAOLogicV1 public immutable daoProxy;
26: INounsDAOExecutor public immutable timelockV1;
27: bool public immutable deployTimelockV2Harness; // should be true only for testnets
Consider importing OZ first, then all interfaces, then all utils.
File: NounsDAOV3Proposals.sol
20: import './NounsDAOInterfaces.sol';
21: import { NounsDAOV3DynamicQuorum } from './NounsDAOV3DynamicQuorum.sol';
22: import { NounsDAOV3Fork } from './fork/NounsDAOV3Fork.sol';
23: import { SignatureChecker } from '@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol';
24: import { ECDSA } from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
25: import { SafeCast } from '@openzeppelin/contracts/utils/math/SafeCast.sol';
File: NounsDAOV3Votes.sol
20: import './NounsDAOInterfaces.sol';
21: import { NounsDAOV3Proposals } from './NounsDAOV3Proposals.sol';
22: import { SafeCast } from '@openzeppelin/contracts/utils/math/SafeCast.sol';
File: NounsDAOForkEscrow.sol
20: import { NounsTokenLike } from '../NounsDAOInterfaces.sol';
21: import { IERC721Receiver } from '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
File: NounsDAOV3Fork.sol
20: import { NounsDAOStorageV3, INounsDAOForkEscrow, INounsDAOExecutorV2 } from '../NounsDAOInterfaces.sol';
21: import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
22: import { NounsTokenFork } from './newdao/token/NounsTokenFork.sol';
File: NounsAuctionHouseFork.sol
32: import { PausableUpgradeable } from '@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol';
33: import { ReentrancyGuardUpgradeable } from '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol';
34: import { OwnableUpgradeable } from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
35: import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
36: import { INounsAuctionHouse } from '../../../interfaces/INounsAuctionHouse.sol';
37: import { INounsToken } from '../../../interfaces/INounsToken.sol';
38: import { IWETH } from '../../../interfaces/IWETH.sol';
39: import { UUPSUpgradeable } from '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol';
File: NounsDAOLogicV1Fork.sol
97: import { UUPSUpgradeable } from '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol';
98: import { NounsDAOEventsFork } from './NounsDAOEventsFork.sol';
99: import { NounsDAOStorageV1Fork } from './NounsDAOStorageV1Fork.sol';
100: import { NounsDAOExecutorV2 } from '../../../NounsDAOExecutorV2.sol';
101: import { INounsTokenForkLike } from './INounsTokenForkLike.sol';
102: import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
103: import { ReentrancyGuardUpgradeable } from '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol';
File: INounsTokenFork.sol
20: import { IERC721Upgradeable } from '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol';
21: import { INounsDescriptorMinimal } from '../../../../interfaces/INounsDescriptorMinimal.sol';
22: import { INounsSeeder } from '../../../../interfaces/INounsSeeder.sol';
File: NounsTokenFork.sol
20: import { OwnableUpgradeable } from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
21: import { ERC721CheckpointableUpgradeable } from './base/ERC721CheckpointableUpgradeable.sol';
22: import { INounsDescriptorMinimal } from '../../../../interfaces/INounsDescriptorMinimal.sol';
23: import { INounsSeeder } from '../../../../interfaces/INounsSeeder.sol';
24: import { INounsTokenFork } from './INounsTokenFork.sol';
25: import { IERC721 } from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
26: import { UUPSUpgradeable } from '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol';
27: import { INounsDAOForkEscrow } from '../../../NounsDAOInterfaces.sol';
File: DeployDAOV3NewContractsBase.s.sol
4: import 'forge-std/Script.sol';
5: import { NounsDAOExecutorV2 } from '../contracts/governance/NounsDAOExecutorV2.sol';
6: import { NounsDAOExecutorV2Test } from '../contracts/test/NounsDAOExecutorHarness.sol';
7: import { NounsDAOLogicV1 } from '../contracts/governance/NounsDAOLogicV1.sol';
8: import { NounsDAOLogicV3 } from '../contracts/governance/NounsDAOLogicV3.sol';
9: import { NounsDAOExecutorProxy } from '../contracts/governance/NounsDAOExecutorProxy.sol';
10: import { INounsDAOExecutor } from '../contracts/governance/NounsDAOInterfaces.sol';
11: import { NounsDAOForkEscrow } from '../contracts/governance/fork/NounsDAOForkEscrow.sol';
12: import { NounsTokenFork } from '../contracts/governance/fork/newdao/token/NounsTokenFork.sol';
13: import { NounsAuctionHouseFork } from '../contracts/governance/fork/newdao/NounsAuctionHouseFork.sol';
14: import { NounsDAOLogicV1Fork } from '../contracts/governance/fork/newdao/governance/NounsDAOLogicV1Fork.sol';
15: import { ForkDAODeployer } from '../contracts/governance/fork/ForkDAODeployer.sol';
16: import { ERC20Transferer } from '../contracts/utils/ERC20Transferer.sol';
There is no need to initialize uint
variables to zero as their default value is 0
File: NounsDAOLogicV2.sol
1007: uint256 lower = 0;
File: NounsDAOV3DynamicQuorum.sol
107: uint256 lower = 0;
File: NounsDAOV3Proposals.sol
825: uint256 numSigners = 0;
File: NounsTokenFork.sol
168: uint256 maxNounId = 0;
File: ERC721CheckpointableUpgradeable.sol
193: uint32 lower = 0;
File: ProposeDAOV3UpgradeMainnet.s.sol
75: uint256 i = 0;
File: ProposeDAOV3UpgradeTestnet.s.sol
82: uint256 i = 0;
File: ProposeENSReverseLookupConfigMainnet.s.sol
40: uint256 i = 0;
File: ProposeTimelockMigrationCleanupMainnet.s.sol
59: uint256 i = 0;
A check regarding whether the current value and the new value are the same should be added
File: NounsDAOExecutor.sol
80: function setDelay(uint256 delay_) public {
require(msg.sender == address(this), 'NounsDAOExecutor::setDelay: Call must come from NounsDAOExecutor.');
require(delay_ >= MINIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must exceed minimum delay.');
require(delay_ <= MAXIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must not exceed maximum delay.');
delay = delay_;
emit NewDelay(delay);
}
File: NounsDAOExecutor.sol
97: function setPendingAdmin(address pendingAdmin_) public {
require(
msg.sender == address(this),
'NounsDAOExecutor::setPendingAdmin: Call must come from NounsDAOExecutor.'
);
pendingAdmin = pendingAdmin_;
emit NewPendingAdmin(pendingAdmin);
}
File: NounsDAOExecutorV2.sol
103: function setDelay(uint256 delay_) public {
require(msg.sender == address(this), 'NounsDAOExecutor::setDelay: Call must come from NounsDAOExecutor.');
require(delay_ >= MINIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must exceed minimum delay.');
require(delay_ <= MAXIMUM_DELAY, 'NounsDAOExecutor::setDelay: Delay must not exceed maximum delay.');
delay = delay_;
emit NewDelay(delay_);
}
File: NounsDAOExecutorV2.sol
120: function setPendingAdmin(address pendingAdmin_) public {
require(
msg.sender == address(this),
'NounsDAOExecutor::setPendingAdmin: Call must come from NounsDAOExecutor.'
);
pendingAdmin = pendingAdmin_;
emit NewPendingAdmin(pendingAdmin_);
}
File: NounsAuctionHouseFork.sol
102: function settleCurrentAndCreateNewAuction() external override nonReentrant whenNotPaused {
_settleAuction();
_createAuction();
}
File: NounsAuctionHouseFork.sol
111: function settleAuction() external override whenPaused nonReentrant {
_settleAuction();
}
File: NounsAuctionHouseFork.sol
180: function setTimeBuffer(uint256 _timeBuffer) external override onlyOwner {
timeBuffer = _timeBuffer;
emit AuctionTimeBufferUpdated(_timeBuffer);
}
File: NounsAuctionHouseFork.sol
190: function setReservePrice(uint256 _reservePrice) external override onlyOwner {
reservePrice = _reservePrice;
emit AuctionReservePriceUpdated(_reservePrice);
}
File: NounsAuctionHouseFork.sol
200: function setMinBidIncrementPercentage(uint8 _minBidIncrementPercentage) external override onlyOwner {
minBidIncrementPercentage = _minBidIncrementPercentage;
emit AuctionMinBidIncrementPercentageUpdated(_minBidIncrementPercentage);
}
File: NounsTokenFork.sol
198: function setContractURIHash(string memory newContractURIHash) external onlyOwner {
_contractURIHash = newContractURIHash;
}
File: NounsTokenFork.sol
240: function setMinter(address _minter) external override onlyOwner whenMinterNotLocked {
minter = _minter;
emit MinterUpdated(_minter);
}
File: NounsTokenFork.sol
260: function setDescriptor(INounsDescriptorMinimal _descriptor) external override onlyOwner whenDescriptorNotLocked {
descriptor = _descriptor;
emit DescriptorUpdated(_descriptor);
}
File: NounsTokenFork.sol
280: function setSeeder(INounsSeeder _seeder) external override onlyOwner whenSeederNotLocked {
seeder = _seeder;
emit SeederUpdated(_seeder);
}
File: NounsDAOInterfaces.sol
576: interface NounsTokenLike {
Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length Reference: https://docs.soliditylang.org/en/v0.8.10/style-guide.html#maximum-line-length
File: NounsDAOExecutor.sol
20: // https://github.com/compound-finance/compound-protocol/blob/20abad28055a2f91df48a90f8bb6009279a4cb35/contracts/Timelock.sol
File: NounsDAOExecutor.sol
22: // Timelock.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.
File: NounsDAOExecutor.sol
25: // Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause
File: NounsDAOExecutor.sol
28: // NounsDAOExecutor.sol modifies Timelock to use Solidity 0.8.x receive(), fallback(), and built-in over/underflow protection
File: NounsDAOExecutor.sol
29: // This contract acts as executor of Nouns DAO governance and its treasury, so it has been modified to accept ETH.
File: NounsDAOExecutorV2.sol
20: // https://github.com/compound-finance/compound-protocol/blob/20abad28055a2f91df48a90f8bb6009279a4cb35/contracts/Timelock.sol
File: NounsDAOExecutorV2.sol
22: // Timelock.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.
File: NounsDAOExecutorV2.sol
25: // Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause
File: NounsDAOExecutorV2.sol
31: // NounsDAOExecutor.sol modifies Timelock to use Solidity 0.8.x receive(), fallback(), and built-in over/underflow protection
File: NounsDAOExecutorV2.sol
32: // This contract acts as executor of Nouns DAO governance and its treasury, so it has been modified to accept ETH.
File: NounsDAOExecutorV2.sol
38: // - `GRACE_PERIOD` has been increased from 14 days to 21 days to allow more time in case of a forking period
File: NounsDAOExecutorV2.sol
84: /// @dev increased grace period from 14 days to 21 days to allow more time in case of a forking period
File: NounsDAOInterfaces.sol
20: // https://github.com/compound-finance/compound-protocol/blob/b9b14038612d846b83f8a009a82c38974ff2dcfe/contracts/Governance/GovernorBravoInterfaces.sol
File: NounsDAOInterfaces.sol
22: // GovernorBravoInterfaces.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.
File: NounsDAOInterfaces.sol
25: // Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause
File: NounsDAOInterfaces.sol
28: // NounsDAOEvents, NounsDAOProxyStorage, NounsDAOStorageV1 add support for changes made by Nouns DAO to GovernorBravo.sol
File: NounsDAOInterfaces.sol
290: /// @notice The basis point number of votes required in order for a voter to become a proposer. *DIFFERS from GovernerBravo
File: NounsDAOInterfaces.sol
390: /// @notice The basis point number of votes required in order for a voter to become a proposer. *DIFFERS from GovernerBravo
File: NounsDAOInterfaces.sol
668: /// @notice The basis point number of votes required in order for a voter to become a proposer. *DIFFERS from GovernerBravo
File: NounsDAOInterfaces.sol
293: /// @notice The basis point number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed. *DIFFERS from GovernerBravo
File: NounsDAOInterfaces.sol
393: /// @notice The basis point number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed. *DIFFERS from GovernerBravo
File: NounsDAOInterfaces.sol
670: /// @notice The basis point number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed. *DIFFERS from GovernerBravo
File: NounsDAOInterfaces.sol
316: /// @notice The number of votes needed to create a proposal at the time of proposal creation. *DIFFERS from GovernerBravo
File: NounsDAOInterfaces.sol
416: /// @notice The number of votes needed to create a proposal at the time of proposal creation. *DIFFERS from GovernerBravo
File: NounsDAOInterfaces.sol
513: /// @notice The number of votes needed to create a proposal at the time of proposal creation. *DIFFERS from GovernerBravo
File: NounsDAOInterfaces.sol
724: /// @notice The number of votes needed to create a proposal at the time of proposal creation. *DIFFERS from GovernerBravo
File: NounsDAOInterfaces.sol
796: /// @notice The number of votes needed to create a proposal at the time of proposal creation. *DIFFERS from GovernerBravo
File: NounsDAOInterfaces.sol
318: /// @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
File: NounsDAOInterfaces.sol
418: /// @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
File: NounsDAOInterfaces.sol
726: /// @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
File: NounsDAOInterfaces.sol
320: /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
File: NounsDAOInterfaces.sol
420: /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
File: NounsDAOInterfaces.sol
517: /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
File: NounsDAOInterfaces.sol
728: /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
File: NounsDAOInterfaces.sol
800: /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
File: NounsDAOInterfaces.sol
491: /// @notice The minimum basis point number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed.
File: NounsDAOInterfaces.sol
833: /// @notice The minimum basis point number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed.
File: NounsDAOInterfaces.sol
493: /// @notice The maximum basis point number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed.
File: NounsDAOInterfaces.sol
835: /// @notice The maximum basis point number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed.
File: NounsDAOInterfaces.sol
496: /// @dev Assumed to be fixed point integer with 6 decimals, i.e 0.2 is represented as 0.2 * 1e6 = 200000
File: NounsDAOInterfaces.sol
838: /// @dev Assumed to be fixed point integer with 6 decimals, i.e 0.2 is represented as 0.2 * 1e6 = 200000
File: NounsDAOInterfaces.sol
515: /// @notice The minimum 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
File: NounsDAOInterfaces.sol
798: /// @notice The minimum 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
File: NounsDAOInterfaces.sol
687: /// @notice user => sig => isCancelled: signatures that have been cancelled by the signer and are no longer valid
File: NounsDAOInterfaces.sol
689: /// @notice The number of blocks before voting ends during which the objection period can be initiated
File: NounsDAOInterfaces.sol
713: /// @notice The proposal at which to start using `startBlock` instead of `creationBlock` for vote snapshots
File: NounsDAOInterfaces.sol
714: /// @dev Make sure this stays the last variable in this struct, so we can delete it in the next version
File: NounsDAOInterfaces.sol
762: /// @notice Starts at 0 and is set to the block at which the objection period ends when the objection period is initiated
File: NounsDAOInterfaces.sol
826: /// @notice Starts at 0 and is set to the block at which the objection period ends when the objection period is initiated
File: NounsDAOLogicV2.sol
20: // https://github.com/compound-finance/compound-protocol/blob/b9b14038612d846b83f8a009a82c38974ff2dcfe/contracts/Governance/GovernorBravoDelegate.sol
File: NounsDAOLogicV2.sol
22: // GovernorBravoDelegate.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.
File: NounsDAOLogicV2.sol
25: // Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause
File: NounsDAOLogicV2.sol
97: /// @notice The vote refund gas overhead, including 7K for ETH transfer and 29K for general transaction overhead
File: NounsDAOLogicV2.sol
622: /// @notice: Unlike GovernerBravo, votes are considered from the block the proposal was created in order to normalize quorumVotes and proposalThreshold metrics
File: NounsDAOLogicV3.sol
20: // https://github.com/compound-finance/compound-protocol/blob/b9b14038612d846b83f8a009a82c38974ff2dcfe/contracts/Governance/GovernorBravoDelegate.sol
File: NounsDAOLogicV3.sol
22: // GovernorBravoDelegate.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.
File: NounsDAOLogicV3.sol
25: // Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause
File: NounsDAOLogicV3.sol
33: // - Proposal editing: allowing proposers to update their proposal’s transactions and text description,
File: NounsDAOLogicV3.sol
34: // during the Updatable period only, which is the state upon proposal creation. Editing also works with signatures,
File: NounsDAOLogicV3.sol
36: // - Propose by signature: allowing Nouners and delegates to pool their voting power towards submitting a proposal,
File: NounsDAOLogicV3.sol
37: // by submitting their signature, instead of the current approach where sponsors must delegate their votes to help
File: NounsDAOLogicV3.sol
39: // - Objection-only Period: a conditional voting period that gets activated upon a last-minute proposal swing
File: NounsDAOLogicV3.sol
42: // - Votes snapshot after voting delay: moving votes snapshot up, to provide Nouners with reaction time per proposal,
File: NounsDAOProxy.sol
20: // https://github.com/compound-finance/compound-protocol/blob/b9b14038612d846b83f8a009a82c38974ff2dcfe/contracts/Governance/GovernorBravoDelegator.sol
File: NounsDAOProxy.sol
22: // GovernorBravoDelegator.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.
File: NounsDAOProxy.sol
25: // Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause
File: NounsDAOProxy.sol
29: // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5c8746f56b4bed8cc9e0e044f5f69ab2f9428ce1/contracts/proxy/Proxy.sol
File: NounsDAOProxy.sol
34: // The fallback() and receive() functions of Proxy.sol have been used to allow Solidity > 0.6.0 compatibility
File: NounsDAOV3Admin.sol
138: /// @notice Upper bound for forking period. If forking period is too high it can block proposals for too long.
File: NounsDAOV3Proposals.sol
392: // without this check it's possible to run through this function and update a proposal without signatures
File: NounsDAOV3Proposals.sol
412: // To avoid the gas cost of having to search signers in proposal.signers, we're assuming the sigs we get
File: NounsDAOV3Votes.sol
56: /// @notice The vote refund gas overhead, including 7K for ETH transfer and 29K for general transaction overhead
File: NounsDAOV3Votes.sol
221: /// @notice: Unlike GovernerBravo, votes are considered from the block the proposal was created in order to normalize quorumVotes and proposalThreshold metrics
File: NounsDAOV3Votes.sol
322: // The idea is to temporarily use this code that would still use `creationBlock` until all proposals are using
File: NounsDAOV3Votes.sol
323: // `startBlock`, then we can deploy a quick DAO fix that removes this line and only uses `startBlock`.
File: NounsDAOForkEscrow.sol
60: /// @notice Number of tokens in escrow in the current fork contributing to the fork threshold. They can be unescrowed.
File: NounsAuctionHouseFork.sol
21: // https://github.com/ourzora/auction-house/blob/54a12ec1a6cf562e49f0a4917990474b11350a2d/contracts/AuctionHouse.sol
File: NounsDAOLogicV1Fork.sol
21: // https://github.com/compound-finance/compound-protocol/blob/b9b14038612d846b83f8a009a82c38974ff2dcfe/contracts/Governance/GovernorBravoDelegate.sol
File: NounsDAOLogicV1Fork.sol
23: // GovernorBravoDelegate.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.
File: NounsDAOLogicV1Fork.sol
26: // Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause
File: NounsDAOLogicV1Fork.sol
30: // - `quit(tokenIds)`, a function that allows token holders to quit the DAO, taking their pro rata funds,
File: NounsDAOLogicV1Fork.sol
33: // - `adjustedTotalSupply`, the total supply calculation used in DAO functions like quorum and proposal threshold, in
File: NounsDAOLogicV1Fork.sol
34: // which the DAO exludes tokens held by the treasury, such that tokens used to quit the DAO are not counted.
File: NounsDAOLogicV1Fork.sol
38: // - A new proposals getter function, since adding new fields to Proposal results in the default getter hitting a
File: NounsDAOLogicV1Fork.sol
41: // - A new Proposal field: `creationBlock`, used to resolve the `votingDelay` bug, in which editing `votingDelay` would
File: NounsDAOLogicV1Fork.sol
45: // - The proxy pattern from Compound's old Transparent-like proxy, to OpenZeppelin's recommended UUPS pattern.
File: NounsDAOLogicV1Fork.sol
49: // - includes a new 'delayed governance' feature which gives forkers from the original DAO time to claim their tokens
File: NounsDAOLogicV1Fork.sol
50: // with this new DAO; proposals are not allowed until all tokens are claimed, or until the delay expiration
File: NounsDAOLogicV1Fork.sol
53: // - `cancel` bugfix, allowing proposals to be canceled by anyone if the proposer's vote balance is equal to proposal
File: NounsDAOLogicV1Fork.sol
56: // - Removes the vetoer role and logic related to it. The quit function provides minority protection instead of the
File: NounsDAOLogicV1Fork.sol
229: // Capture balances to send before actually sending them, to avoid the risk of external calls changing balances.
File: NounsDAOLogicV1Fork.sol
623: /// @notice: Unlike GovernerBravo, votes are considered from the block the proposal was created in order to normalize quorumVotes and proposalThreshold metrics
File: NounsDAOStorageV1Fork.sol
33: /// @notice The basis point number of votes to exceed in order for a voter to become a proposer. *DIFFERS from GovernerBravo
File: NounsDAOStorageV1Fork.sol
36: /// @notice The basis point number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed. *DIFFERS from GovernerBravo
File: NounsDAOStorageV1Fork.sol
63: /// @notice The number of votes needed to create a proposal at the time of proposal creation. *DIFFERS from GovernerBravo
File: NounsDAOStorageV1Fork.sol
124: /// @notice The number of votes needed to create a proposal at the time of proposal creation. *DIFFERS from GovernerBravo
File: NounsDAOStorageV1Fork.sol
65: /// @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
File: NounsDAOStorageV1Fork.sol
67: /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
File: NounsDAOStorageV1Fork.sol
128: /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
File: NounsDAOStorageV1Fork.sol
126: /// @notice The minimum 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
File: NounsTokenFork.sol
57: /// @notice The escrow contract used to verify ownership of the original Nouns in the post-fork claiming process
File: NounsTokenFork.sol
63: /// @notice How many tokens are still available to be claimed by Nouners who put their original Nouns in escrow
File: NounsTokenFork.sol
66: /// @notice The forking period expiration timestamp, after which new tokens cannot be claimed by the original DAO
File: NounsTokenFork.sol
180: // During a forking period, people can buy new Nouns on auction, with a higher ID than the auction ID at forking
File: ERC721CheckpointableUpgradeable.sol
19: // ERC721CheckpointableUpgradeable.sol is a modified version of ERC721Checkpointable.sol in this repository.
File: ERC721CheckpointableUpgradeable.sol
21: // https://github.com/compound-finance/compound-protocol/blob/ae4388e780a8d596d97619d9704a931a2752c2bc/contracts/Governance/Comp.sol
File: ERC721CheckpointableUpgradeable.sol
26: // Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause
File: ERC721CheckpointableUpgradeable.sol
29: // - Inherits from OpenZeppelin's ERC721EnumerableUpgradeable.sol, removing the original modification Nouns made to
File: ERC721CheckpointableUpgradeable.sol
30: // ERC721.sol, where for each mint two Transfer events were emitted; this modified implementation sticks with the
File: ERC721CheckpointableUpgradeable.sol
32: // - More importantly, this inheritance change makes the token upgradable, which we deemed important in the context of
File: ERC721CheckpointableUpgradeable.sol
33: // forks, in order to give new Nouns forks enough of a chance to modify their contracts to the new DAO's needs.
File: ERC721CheckpointableUpgradeable.sol
34: // - Fixes a critical bug in `delegateBySig`, where the previous version allowed delegating to address zero, which then
File: ERC721CheckpointableUpgradeable.sol
35: // reverts whenever that owner tries to delegate anew or transfer their tokens. The fix is simply to revert on any
File: ERC721CheckpointableUpgradeable.sol
44: // - `_transferTokens()` is renamed `_beforeTokenTransfer()` and adapted to hook into OpenZeppelin's ERC721 hooks.
File: ERC721CheckpointableUpgradeable.sol
51: /// @notice Defines decimals as per ERC-20 convention to make integrations with 3rd party governance platforms easier
File: ERC721CheckpointableUpgradeable.sol
115: /// @notice Differs from `_transferTokens()` to use `delegates` override method to simulate auto-delegation
File: ERC721CheckpointableUpgradeable.sol
210: /// @notice differs from `_delegate()` in `Comp.sol` to use `delegates` override method to simulate auto-delegation
File: NounsDAOLogicV2.sol
197: function propose(
File: NounsDAOLogicV1Fork.sol
272: function propose(
File: ProposeDAOV3UpgradeMainnet.s.sol
54: function propose(
File: ProposeDAOV3UpgradeTestnet.s.sol
62: function propose(
When changing state variables events are not emitted. Emitting events allows monitoring activities with off-chain monitoring tools.
File: NounsDAOInterfaces.sol
601: function setApprovalForAll(address operator, bool approved) external;
File: NounsAuctionHouseFork.sol
102: function settleCurrentAndCreateNewAuction() external override nonReentrant whenNotPaused {
File: NounsTokenFork.sol
198: function setContractURIHash(string memory newContractURIHash) external onlyOwner {
It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability. https://docs.soliditylang.org/en/v0.8.15/natspec-format.html
All in-scope contracts
NatSpec comments should be increased in contracts
File: NounsDAOExecutorV2.sol
84: /// @dev increased grace period from 14 days to 21 days to allow more time in case of a forking period
uint256 public constant GRACE_PERIOD = 21 days;
uint256 public constant MINIMUM_DELAY = 2 days;
uint256 public constant MAXIMUM_DELAY = 30 days;
address public admin;
address public pendingAdmin;
uint256 public delay;
mapping(bytes32 => bool) public queuedTransactions;
function initialize(address admin_, uint256 delay_) public virtual initializer {
File: NounsDAOLogicV2.sol
932: /**
* @notice Quorum votes required for a specific proposal to succeed
* Differs from `GovernerBravo` which uses fixed amount
*/
function quorumVotes(uint256 proposalId) public view returns (uint256) {
File: NounsDAOLogicV3.sol
373: /**
* @notice Executes a queued proposal on timelockV1 if eta has passed
* This is only required for proposal that were queued on timelockV1, but before the upgrade to DAO V3.
* These proposals will not have the `executeOnTimelockV1` bool turned on.
*/
function executeOnTimelockV1(uint256 proposalId) external {
File: NounsDAOLogicV3.sol
872: /**
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
* DYNAMIC QUORUM
* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
/**
* @notice Quorum votes required for a specific proposal to succeed
* Differs from `GovernerBravo` which uses fixed amount
*/
function quorumVotes(uint256 proposalId) public view returns (uint256) {
File: NounsDAOLogicV3.sol
931: /**
* @notice Get all quorum params checkpoints
*/
function quorumParamsCheckpoints() public view returns (DynamicQuorumParamsCheckpoint[] memory) {
938: /**
* @notice Get a quorum params checkpoint by its index
*/
function quorumParamsCheckpoints(uint256 index) public view returns (DynamicQuorumParamsCheckpoint memory) {
File: NounsDAOV3Admin.sol
297: /**
* @notice Begins transition of vetoer. The newPendingVetoer must call _acceptVetoer to finalize the transfer.
* @param newPendingVetoer New Pending Vetoer
*/
function _setPendingVetoer(NounsDAOStorageV3.StorageV3 storage ds, address newPendingVetoer) public {
File: NounsDAOV3Admin.sol
311: /**
* @notice Called by the pendingVetoer to accept role and update vetoer
*/
function _acceptVetoer(NounsDAOStorageV3.StorageV3 storage ds) external {
File: NounsDAOV3Admin.sol
465: /**
* @notice Withdraws all the ETH in the contract. This is callable only by the admin (timelock).
*/
function _withdraw(NounsDAOStorageV3.StorageV3 storage ds) external onlyAdmin(ds) returns (uint256, bool) {
File: NounsDAOV3DynamicQuorum.sol
28: /**
* @notice Quorum votes required for a specific proposal to succeed
* Differs from `GovernerBravo` which uses fixed amount
*/
function quorumVotes(NounsDAOStorageV3.StorageV3 storage ds, uint256 proposalId) internal view returns (uint256) {
File: NounsDAOV3DynamicQuorum.sol
70: /**
* @notice returns the dynamic quorum parameters values at a certain block number
* @dev The checkpoints array must not be empty, and the block number must be higher than or equal to
* the block of the first checkpoint
* @param blockNumber_ the block number to get the params at
* @return The dynamic quorum parameters that were set at the given block number
*/
function getDynamicQuorumParamsAt(NounsDAOStorageV3.StorageV3 storage ds, uint256 blockNumber_)
internal
view
returns (NounsDAOStorageV3.DynamicQuorumParams memory)
{
File: NounsDAOV3DynamicQuorum.sol
123: /**
* @notice Current min quorum votes using Nouns adjusted total supply
*/
function minQuorumVotes(NounsDAOStorageV3.StorageV3 storage ds, uint256 adjustedTotalSupply)
internal
view
returns (uint256)
{
File: NounsDAOV3DynamicQuorum.sol
134: /**
* @notice Current max quorum votes using Nouns adjusted total supply
*/
function maxQuorumVotes(NounsDAOStorageV3.StorageV3 storage ds, uint256 adjustedTotalSupply)
internal
view
returns (uint256)
{
File: NounsDAOV3Proposals.sol
261: /**
* @notice Invalidates a signature that may be used for signing a proposal.
* Once a signature is canceled, the sender can no longer use it again.
* If the sender changes their mind and want to sign the proposal, they can change the expiry timestamp
* in order to produce a new signature.
* The signature will only be invalidated when used by the sender. If used by a different account, it will
* not be invalidated.
* @param sig The signature to cancel
*/
function cancelSig(NounsDAOStorageV3.StorageV3 storage ds, bytes calldata sig) external {
File: NounsDAOV3Proposals.sol
354: /**
* @notice Updates the proposal's description. Only the proposer can update it, and only during the updateable period.
* @param proposalId Proposal's id
* @param description Updated description of the proposal
* @param updateMessage Short message to explain the update
*/
function updateProposalDescription(
NounsDAOStorageV3.StorageV3 storage ds,
uint256 proposalId,
string calldata description,
string calldata updateMessage
) external {
File: NounsDAOV3Proposals.sol
372: /**
* @notice Update a proposal's transactions and description that was created with proposeBySigs.
* Only the proposer can update it, during the updateable period.
* Requires the original signers to sign the update.
* @param proposalId Proposal's id
* @param proposerSignatures Array of signers who have signed the proposal and their signatures.
* @dev The signatures follow EIP-712. See `UPDATE_PROPOSAL_TYPEHASH` in NounsDAOV3Proposals.sol
* @param txs Updated transactions for the proposal
* @param description Updated description of the proposal
* @param updateMessage Short message to explain the update
*/
function updateProposalBySigs(
NounsDAOStorageV3.StorageV3 storage ds,
uint256 proposalId,
NounsDAOStorageV3.ProposerSignature[] memory proposerSignatures,
ProposalTxs memory txs,
string memory description,
string memory updateMessage
) external {
File: NounsDAOV3Proposals.sol
434: /**
* @notice Queues a proposal of state succeeded
* @param proposalId The id of the proposal to queue
*/
function queue(NounsDAOStorageV3.StorageV3 storage ds, uint256 proposalId) external {
File: NounsDAOV3Proposals.sol
475: /**
* @notice Executes a queued proposal if eta has passed
* @param proposalId The id of the proposal to execute
*/
function execute(NounsDAOStorageV3.StorageV3 storage ds, uint256 proposalId) external {
File: NounsDAOV3Proposals.sol
485: /**
* @notice Executes a queued proposal on timelockV1 if eta has passed
* This is only required for proposal that were queued on timelockV1, but before the upgrade to DAO V3.
* These proposals will not have the `executeOnTimelockV1` bool turned on.
*/
function executeOnTimelockV1(NounsDAOStorageV3.StorageV3 storage ds, uint256 proposalId) external {
File: NounsDAOV3Proposals.sol
532: /**
* @notice Vetoes a proposal only if sender is the vetoer and the proposal has not been executed.
* @param proposalId The id of the proposal to veto
*/
function veto(NounsDAOStorageV3.StorageV3 storage ds, uint256 proposalId) external {
File: NounsDAOV3Proposals.sol
566: /**
* @notice Cancels a proposal only if sender is the proposer or a signer, or proposer & signers voting power
* dropped below proposal threshold
* @param proposalId The id of the proposal to cancel
*/
function cancel(NounsDAOStorageV3.StorageV3 storage ds, uint256 proposalId) external {
File: NounsDAOV3Proposals.sol
669: /**
* @notice Gets actions of a proposal
* @param proposalId the id of the proposal
* @return targets
* @return values
* @return signatures
* @return calldatas
*/
function getActions(NounsDAOStorageV3.StorageV3 storage ds, uint256 proposalId)
internal
view
returns (
address[] memory targets,
uint256[] memory values,
string[] memory signatures,
bytes[] memory calldatas
)
{
File: NounsDAOV3Proposals.sol
691: /**
* @notice Gets the receipt for a voter on a given proposal
* @param proposalId the id of proposal
* @param voter The address of the voter
* @return The voting receipt
*/
function getReceipt(
NounsDAOStorageV3.StorageV3 storage ds,
uint256 proposalId,
address voter
) internal view returns (NounsDAOStorageV3.Receipt memory) {
File: NounsDAOV3Proposals.sol
773: /**
* @notice Current proposal threshold using Noun Total Supply
* Differs from `GovernerBravo` which uses fixed amount
*/
function proposalThreshold(NounsDAOStorageV3.StorageV3 storage ds, uint256 adjustedTotalSupply)
internal
view
returns (uint256)
{
File: NounsDAOV3Proposals.sol
794: /**
* @notice reverts if `proposer` is the proposer or signer of an active proposal.
* This is a spam protection mechanism to limit the number of proposals each noun can back.
*/
function checkNoActiveProp(NounsDAOStorageV3.StorageV3 storage ds, address proposer) internal view {
File: NounsDAOV3Proposals.sol
924: /// @notice Maintains backwards compatibility with GovernorBravo events
emit ProposalCreated(
newProposal.id,
msg.sender,
txs.targets,
txs.values,
txs.signatures,
txs.calldatas,
newProposal.startBlock,
newProposal.endBlock,
description
);
/// @notice V1: Updated event with `proposalThreshold` and `quorumVotes` `minQuorumVotes`
/// @notice V2: `quorumVotes` changed to `minQuorumVotes`
/// @notice V3: Added signers and updatePeriodEndBlock
emit ProposalCreatedWithRequirements(
newProposal.id,
msg.sender,
signers,
txs.targets,
txs.values,
txs.signatures,
txs.calldatas,
newProposal.startBlock,
newProposal.endBlock,
newProposal.updatePeriodEndBlock,
newProposal.proposalThreshold,
minQuorumVotes,
description
);
}
function checkPropThreshold(
NounsDAOStorageV3.StorageV3 storage ds,
uint256 votes,
uint256 adjustedTotalSupply
) internal view returns (uint256 propThreshold) {
File: NounsDAOV3Votes.sol
78: /**
* @notice Cast a vote for a proposal, asking the DAO to refund gas costs.
* Users with > 0 votes receive refunds. Refunds are partial when using a gas priority fee higher than the DAO's cap.
* Refunds are partial when the DAO's balance is insufficient.
* No refund is sent when the DAO's balance is empty. No refund is sent to users with no votes.
* Voting takes place regardless of refund success.
* @param proposalId The id of the proposal to vote on
* @param support The support value for the vote. 0=against, 1=for, 2=abstain
* @dev Reentrancy is defended against in `castVoteInternal` at the `receipt.hasVoted == false` require statement.
*/
function castRefundableVote(
NounsDAOStorageV3.StorageV3 storage ds,
uint256 proposalId,
uint8 support
) external {
File: NounsDAOV3Votes.sol
96: /**
* @notice Cast a vote for a proposal, asking the DAO to refund gas costs.
* Users with > 0 votes receive refunds. Refunds are partial when using a gas priority fee higher than the DAO's cap.
* Refunds are partial when the DAO's balance is insufficient.
* No refund is sent when the DAO's balance is empty. No refund is sent to users with no votes.
* Voting takes place regardless of refund success.
* @param proposalId The id of the proposal to vote on
* @param support The support value for the vote. 0=against, 1=for, 2=abstain
* @param reason The reason given for the vote by the voter
* @dev Reentrancy is defended against in `castVoteInternal` at the `receipt.hasVoted == false` require statement.
*/
function castRefundableVoteWithReason(
NounsDAOStorageV3.StorageV3 storage ds,
uint256 proposalId,
uint8 support,
string calldata reason
) external {
File: NounsDAOV3Votes.sol
137: /**
* @notice Cast a vote for a proposal with a reason
* @param proposalId The id of the proposal to vote on
* @param support The support value for the vote. 0=against, 1=for, 2=abstain
* @param reason The reason given for the vote by the voter
*/
function castVoteWithReason(
NounsDAOStorageV3.StorageV3 storage ds,
uint256 proposalId,
uint8 support,
string calldata reason
) external {
File: ForkDAODeployer.sol
131: /**
* @dev Used to prevent the 'Stack too deep' error in the main deploy function.
*/
function initDAO(
address governor,
address treasury,
address token,
NounsDAOExecutorV2 originalTimelock
) internal {
File: ForkDAODeployer.sol
153: /**
* @dev Used to prevent the 'Stack too deep' error in the main deploy function.
*/
function getOriginalTimelock(INounsDAOForkEscrow forkEscrow) internal view returns (NounsDAOExecutorV2) {
File: ForkDAODeployer.sol
161: /**
* @dev Used to prevent the 'Stack too deep' error in the main deploy function.
*/
function getOriginalAuction(INounsDAOForkEscrow forkEscrow) internal view returns (NounsAuctionHouse) {
File: NounsDAOV3Fork.sol
90: /**
* @notice Withdraw Nouns from the fork escrow. Only possible if the fork has not been executed.
* Only allowed to withdraw tokens that the sender has escrowed.
* @param tokenIds the tokenIds to withdraw
*/
function withdrawFromForkEscrow(NounsDAOStorageV3.StorageV3 storage ds, uint256[] calldata tokenIds) external {
File: NounsDAOV3Fork.sol
104: /**
* @notice Execute the fork. Only possible if the fork threshold has been exceeded.
* This will deploy a new DAO and send the prorated part of the treasury to the new DAO's treasury.
* This will also close the active escrow and all nouns in the escrow will belong to the original DAO.
* @return forkTreasury The address of the new DAO's treasury
* @return forkToken The address of the new DAO's token
*/
function executeFork(NounsDAOStorageV3.StorageV3 storage ds)
external
returns (address forkTreasury, address forkToken)
{
File: NounsDAOV3Fork.sol
134: /**
* @notice Joins a fork while a fork is active
* Sends the tokens to the timelock contract.
* Sends a prorated part of the treasury to the new fork DAO's treasury.
* Mints new tokens in the new fork DAO with the same token ids.
* @param tokenIds the tokenIds to send to the DAO in exchange for joining the fork
*/
function joinFork(
NounsDAOStorageV3.StorageV3 storage ds,
uint256[] calldata tokenIds,
uint256[] calldata proposalIds,
string calldata reason
) external {
File: NounsDAOV3Fork.sol
162: /**
* @notice Withdraws nouns from the fork escrow to the treasury after the fork has been executed
* @dev Only the DAO can call this function
* @param tokenIds the tokenIds to withdraw
*/
function withdrawDAONounsFromEscrowToTreasury(NounsDAOStorageV3.StorageV3 storage ds, uint256[] calldata tokenIds)
external
{
File: NounsDAOV3Fork.sol
173: /**
* @notice Withdraws nouns from the fork escrow after the fork has been executed to an address other than the treasury
* @dev Only the DAO can call this function
* @param tokenIds the tokenIds to withdraw
* @param to the address to send the nouns to
*/
function withdrawDAONounsFromEscrowIncreasingTotalSupply(
NounsDAOStorageV3.StorageV3 storage ds,
uint256[] calldata tokenIds,
address to
) external {
File: NounsDAOV3Fork.sol
205: /**
* @notice Returns the required number of tokens to escrow to trigger a fork
*/
function forkThreshold(NounsDAOStorageV3.StorageV3 storage ds) public view returns (uint256) {
File: NounsDAOV3Fork.sol
212: /**
* @notice Returns the number of tokens currently in escrow, contributing to the fork threshold
*/
function numTokensInForkEscrow(NounsDAOStorageV3.StorageV3 storage ds) public view returns (uint256) {
File: NounsDAOV3Fork.sol
219: /**
* @notice Returns the number of nouns in supply minus nouns owned by the DAO, i.e. held in the treasury or in an
* escrow after it has closed.
* This is used when calculating proposal threshold, quorum, fork threshold & treasury split.
*/
function adjustedTotalSupply(NounsDAOStorageV3.StorageV3 storage ds) internal view returns (uint256) {
File: NounsDAOV3Fork.sol
228: /**
* @notice Returns true if noun holders can currently join a fork
*/
function isForkPeriodActive(NounsDAOStorageV3.StorageV3 storage ds) internal view returns (bool) {
File: NounsDAOV3Fork.sol
235: /**
* @notice Sends part of the DAO's treasury to the `newDAOTreasury` address.
* The amount sent is proportional to the `tokenCount` out of `totalSupply`.
* Sends ETH and ERC20 tokens listed in `ds.erc20TokensToIncludeInFork`.
*/
function sendProRataTreasury(
NounsDAOStorageV3.StorageV3 storage ds,
address newDAOTreasury,
uint256 tokenCount,
uint256 totalSupply
) internal {
File: NounsAuctionHouseFork.sol
176: /**
* @notice Set the auction time buffer.
* @dev Only callable by the owner.
*/
function setTimeBuffer(uint256 _timeBuffer) external override onlyOwner {
File: NounsAuctionHouseFork.sol
186: /**
* @notice Set the auction reserve price.
* @dev Only callable by the owner.
*/
function setReservePrice(uint256 _reservePrice) external override onlyOwner {
File: NounsAuctionHouseFork.sol
196: /**
* @notice Set the auction minimum bid increment percentage.
* @dev Only callable by the owner.
*/
function setMinBidIncrementPercentage(uint8 _minBidIncrementPercentage) external override onlyOwner {
File: NounsAuctionHouseFork.sol
258: /**
* @notice Transfer ETH. If the ETH transfer fails, wrap the ETH and try send it as WETH.
*/
function _safeTransferETHWithFallback(address to, uint256 amount) internal {
File: NounsAuctionHouseFork.sol
277: /**
* @dev Reverts when `msg.sender` is not the owner of this contract; in the case of Noun DAOs it should be the
* DAO's treasury contract.
*/
function _authorizeUpgrade(address) internal view override onlyOwner {
File: NounsTokenFork.sol
194: /**
* @notice Set the _contractURIHash.
* @dev Only callable by the owner.
*/
function setContractURIHash(string memory newContractURIHash) external onlyOwner {
File: NounsTokenFork.sol
210: /**
* @notice Burn a noun.
*/
function burn(uint256 nounId) public override onlyMinter {
File: NounsTokenFork.sol
218: /**
* @notice A distinct Uniform Resource Identifier (URI) for a given asset.
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(uint256 tokenId) public view override returns (string memory) {
File: NounsTokenFork.sol
227: /**
* @notice Similar to `tokenURI`, but always serves a base64 encoded data URI
* with the JSON contents directly inlined.
*/
function dataURI(uint256 tokenId) public view override returns (string memory) {
File: NounsTokenFork.sol
236: /**
* @notice Set the token minter.
* @dev Only callable by the owner when not locked.
*/
function setMinter(address _minter) external override onlyOwner whenMinterNotLocked