xanoutas/solidity-security-findings huggingface dataset:
USE `safeTransferFrom` INSTEAD OF `transferFrom` FOR ERC721 TRANSFERS
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/main/src/auction/Auction.sol#L192


# Vulnerability details

## Impact

The `_settleAuction()` function uses `transferFrom` to transfer the new token to the highest bidder but the `transferFrom` function doesn't check if the recipient aka the highest bidder is capable of receiving ERC721, in case the highest bidder is a custom contract that doesn't accept ERC721 transfer the token may be lost forever, and because those ERC721 tokens are later used for governance voting it result that some voting power is not used because of those lost tokens.

## Proof of Concept

Instance occurs here : [Auction.sol#L192](https://github.com/code-423n4/2022-09-nouns-builder/blob/main/src/auction/Auction.sol#L192)

The `_settleAuction()` function uses `transferFrom` to transfer the new token to the highest bidder :

```
    // Transfer the token to the highest bidder
    token.transferFrom(address(this), _auction.highestBidder, _auction.tokenId);
```

Which is not recommended even if it uses less gas, and [OpenZeppelin’s documentation](https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#IERC721-transferFrom-address-address-uint256-) also discourages the use of transferFrom(), and recommend using safeTransferFrom() whenever possible.

## Tools Used

Visual Audit

## Recommended Mitigation Steps

Call the  `safeTransferFrom`  method instead of `transferFrom` for NFT tokens transfers in the `_settleAuction()` function inside the Auction contract. Note that the `Auction` contract should inherit the ERC721TokenReceiver contract as a consequence.
proposalThreshold comparison not consistent
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L128
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L363


# Vulnerability details

## Impact
proposalThreshold comparison not consistent. When getVotes(...)==proposalThreshold, one can both propose and cancel a proposal, thus someone could cancel a proposal in the same block if the proposer is on the threshold.

## Proof of Concept
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L128
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L363


## Tools Used

## Recommended Mitigation Steps
chang one of the comparison to be inclusive
no max token supply
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L154


# Vulnerability details

## Impact
No max supply. The token contract lacks the concept of max_token_supply, thus users can print type(uint256).max amount of the tokens, which inevitably keeps token value down

## Proof of Concept
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L154

## Tools Used

## Recommended Mitigation Steps
add a token supply cap
No limitation in addProperties of MetadataRenderer
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/metadata/MetadataRenderer.sol#L179-L199


# Vulnerability details

## Impact

There is no limit of the length of properties in `addProperties` of `MetadataRenderer.sol`. So it is possible to add more than 15 properties via voting. 
In this case `onMinted` will be reverted because it uses index 16 of array tokenAttributes whose length is 16. As a result, mint and auction will fail.

If item count of a property is 0 after `addProperties`, `onMinted` will also fail again. So another validation is needed.

## Recommended Mitigation Steps
Validate the length of properties and item count for each property.
overstepping _propertyId
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/metadata/MetadataRenderer.sol#L140


# Vulnerability details

## Impact
overstepping _propertyId?
I have difficulty following this logic. It seems to me _prorpertyId might get reused in some cases.

## Proof of Concept
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/metadata/MetadataRenderer.sol#L140

## Tools Used

## Recommended Mitigation Steps
The % is not applied to baseTokenId properly
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L118
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L179


# Vulnerability details

## Impact
wrong % calculation. The % is not applied to baseTokenId, so baseTokenId can go beyond 100 given certain founderPct sequence, for example, 23, 19, 17, 13. Thus founders cannot claim some tokenIds for free.

## Proof of Concept
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L118
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L179

## Tools Used

## Recommended Mitigation Steps
if this is the intention then:
    `baseTokenId = (baseTokenId + schedule) % 100; `
Delegatee calling `castVoteBySig()` can be front run by signer.
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L255-L261


# Vulnerability details


A signer can front-run their delegatee by calling themselves `castVote()` before the delegatee calls `castVoteBySig()`. This is effectively griefing the delegatee.


## Impact

Medium


## Proof Of Concept

- Alice owns a token and delegates her voting power to a delegatee by calling `delegateBySig()`
- Alice tracks the transaction mempool and front runs the delegatee calling `castVoteBySig()` by calling `castVote()` and passing a high amount of gas.

## Tools Used

Manual Analysis


## Mitigation

Consider using a storage variable to track when a signer delegatedstheir vote in [_delegate](https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/lib/token/ERC721Votes.sol#L179-L189), and add a check in `castVote()` to revert when they have delegated their vote.
Possible for funds to get stuck in the Auction contract
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/main/src/auction/Auction.sol#L244-L260


# Vulnerability details

## Impact
For projects with a 0% founder percentage ownership, it is possible for the highest bidder of the first auction to not receive the minted token, and funds from the first auction to be stuck in the contract.

## Proof of Concept

In `Auction.sol`, the contract determines if the first auction has been created using the statement `auction.tokenId == 0`, as shown below:
```solidity
244:    function unpause() external onlyOwner {
245:        _unpause();
246:
247:        // If this is the first auction:
248:        if (auction.tokenId == 0) {
249:            // Transfer ownership of the contract to the DAO
250:            transferOwnership(settings.treasury);
251:
252:            // Start the first auction
253:            _createAuction();
254:        }
255:        // Else if the contract was paused and the previous auction was settled:
256:        else if (auction.settled) {
257:            // Start the next auction
258:            _createAuction();
259:        }
260:    }
```

If the check at line 248 passes, the `unpause()` function will directly call `_createAuction()` without checking that the current auction has been settled.

Thus, in a situation where the `tokenId` of the first auction is `0`, the owner could potentially call `unpause()` again without settling the first auction, causing the current auction to be overwritten due to the call to `_createAuction()`. 

This would cause the following:
* Funds deposited by the highest bidder from the first auction would become stuck in the contract
* The highest bidder for the first auction will never receive the minted token

For example:
* A project is deployed with a total percentage ownership of 0% from founders
* The founder calls `unpause()` to start the first auction
  * At line 250, ownership is transferred to the treasury
  * The first auction is started via `_createAuction()` at line 253
* As founders have no ownership percentage of the minted tokens, the first token minted (`tokenId = 0`), will be used as the token for the first auction
* The DAO pauses the auction for some reason via `pause()`
* If the DAO calls `unpause()` without first calling `settleAuction()`
  * A new auction with `tokenId = 1` would be created, overwriting the first auction

In this scenario, funds from the first auction would be unretrievable as the `Auction` contract does not have a function to send any amount of ETH to an address. Furthermore, as `settleAuction()` was not called, the highest bidder would not receive the token of the first auction.

The test in [this gist](https://gist.github.com/MiloTruck/f0329c57864951cda55ffff1bda380fc) demonstrates the above.

## Recommended Mitigation Steps
At line 248, instead of checking `auction.tokenId == 0` to determine if the first auction has been created, check if `auction.startTime == 0` instead.
Hard coded gas limit
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/main/src/auction/Auction.sol#L354


# Vulnerability details

## Impact
Gas prices can vary hugely after hard forks and functions would be broken once the hard coded limit is unreasonably low in that future environment.

## Proof of Concept

https://github.com/code-423n4/2022-09-nouns-builder/blob/main/src/auction/Auction.sol#L354

In line assembly limited the gas to 50,000.

## Tools Used

Manual audit

## Recommended Mitigation Steps

Despite 50,000 gas is limit is high it is not recommended to use any limitation. If the initial intent was to defend against re-entrancy attacks I recommend using security measures specialized for that attack like re-entrancy guard.

## Reference

https://swcregistry.io/docs/SWC-134
51% attack is possible due to poor validation
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L71-L126


# Vulnerability details

## Impact
In `_addFounders` of `Token.sol`, validation is not enough.
Let us say there are 2 founders, Alice and Bob. Alice has 0 `ownershipPct`, and Bob's wallet is `address(0)`. We validate Alice's wallet only in Line 117 of Manager.sol, this setup will run without revert. 
These two founders have 0 percent of tokens because Bob's wallet is `address(0)`. So the first NFT will be minted to 3rd party. And this party will have 100 percent of voting power.


## Recommended Mitigation Steps
Validate founder's `wallet` in `_addFounders`
`quorum()` can return `0` for young DAOs
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L473-L477


# Vulnerability details



`quorum()` currently rounds down, as per Solidity truncation happening in division.
In the case of Nouns, where a DAO can have a `Token` with a low supply and `decimals = 0`, this means the number of votes required for a proposal can be 1 vote lower than expected.

## Impact

Medium


## Proof Of Concept

`quorum` is calculated as follow:

```solidity
472: /// @notice The current number of votes required to be in favor of a proposal in order to reach quorum
473:     function quorum() public view returns (uint256) {
474:         unchecked {
475:             return (settings.token.totalSupply() * settings.quorumThresholdBps) / 10_000;
476:         }
477:     }
```

Assume that `settings.quorumThresholdBps == 1000` (10%) and `settings.token.totalSupply() == 9`

`quorum = 9 * 1000 / 10_000 = 9_000 / 10_000 = 0`.

This means no minimum number of votes in support of a proposal for it to succeed.

When calling `propose`, `proposal.quorumVotes` will hence [be zero](https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L168).

This means [one vote is enough](https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L441) for a proposal to be successful

## Tools Used

Manual Analysis


## Mitigation

You can use the modulo operator to ensure that:
- the `quorum` will always be greater than 1 
- `quorum` will always be rounded up instead of down.

```diff
-             return (settings.token.totalSupply() * settings.quorumThresholdBps) / 10_000;
+             uint256 supplyThreshold = settings.token.totalSupply() * settings.quorumThresholdBps;
+             return supplyThreshold % 10_000 == 0 ? supplyThreshold / 10_000 : supplyThreshold / 10_000 + 1;
```
Governor.sol : Validation check is missing between proposalThresholdBps and quorumThresholdBps 
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L79-L80


# Vulnerability details

## Impact
proposalThresholdBps  and quorumThresholdBps can be set as same value.

## Proof of Concept
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L79-L80

There are no checks like quorumThresholdBps > proposalThresholdBps  .

## Tools Used

Manual code review

## Recommended Mitigation Steps

Always ensure that quorumThresholdBps > proposalThresholdBps  . This need to be considered while deploying and updating the values.
[M2] Founders can schedule unlimited number of tokens for minting
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L82
 https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L108


# Vulnerability details



## Proof of Concept

   When you call `_addfounders()` you pass FounderParams[].  
   
   The variable foundersPct is defined as a `uint256`     
        
       uint256 founderPct = _founders[i].ownershipPct;

   This variable is unsafely casted  to calculate that the totalOwnership is never more that 100 percent

        if ((totalOwnership += uint8(founderPct)) > 100) revert INVALID_FOUNDER_OWNERSHIP();

Then is ok when you assign 

                newFounder.ownershipPct = uint8(founderPct);

However, when you enter the loop to schedule minting you dont limit the loop to the casted value 

That means for example if you set `founderPct` to 256 then it is stored as
ownershipPct = 1.    But the founder will be scheduling 256 tokens.   

    for (uint256 j; j < founderPct; ++j) {@audit high
                    // Get the available token id
                   baseTokenId = _getNextTokenId(baseTokenId);
                    // Store the founder as the recipient
                    tokenRecipient[baseTokenId] = newFounder;
                    emit MintScheduled(baseTokenId, founderId, newFounder);
                    // Update the base token id
                    (baseTokenId += schedule) % 100;
                }   



## Recommended

It is better to do one safecast at the begining

    [-] uint256 founderPct = _founders[i].ownershipPct;
    [+] uint8 founderPct = SafeCast.toUint8(_founders[i].ownershipPct);

  Even better, I believe, is just to change the variable length is the definition of the struct  `FounderParams`
    
    [-] uint256 ownershipPct;
    [+] uint8 ownershipPct;
Approve would revert on  most occasions
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/main/src/lib/token/ERC721.sol#L105


# Vulnerability details

## Impact
Due to the if statement in line 105 of ERC721 contract, the `approve()` call would always revert. 

The if-statement would only pass when the owner of the token `operatorApprovals` to themself , however the call to transferFrom()  would revert due to the check in ERC721Votes._moveDelegateVotes() that ensures there is no delegation to same from and to address.

## Proof of Concept
https://github.com/code-423n4/2022-09-nouns-builder/blob/main/src/lib/token/ERC721.sol#L105

## Tools Used
Manual review

## Recommended Mitigation Steps
1. Change the && to  || 
2. Or It would be quite easier to use a require check similar in the OZ ERC71 library in https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L116-L119
Governor.sol : During initializing, validity check is missed for vetoer
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L76


# Vulnerability details

## Impact
_vetoer can be set as zero address.

## Proof of Concept
https://github.com/code-423n4/2022-09-nouns-
builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/governance/governor/Governor.sol#L69-L76

When we see the initialize function,

        // Ensure non-zero addresses are provided
        if (_treasury == address(0)) revert ADDRESS_ZERO();
        if (_token == address(0)) revert ADDRESS_ZERO();


        // Store the governor settings
        settings.treasury = Treasury(payable(_treasury));
        settings.token = Token(_token);
        settings.vetoer = _vetoer;

treasury and token addresses are validated. but `_vetoer` is missed.

## Tools Used

Manual code review.

## Recommended Mitigation Steps

Add the validation check for vetoer also.
mint will not work due to poor validation
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L88


# Vulnerability details

## Impact
In `_addFounders` of `Token.sol`, validation is not enough. Let us say there is 1 founder, and he has 3 `ownershipPct`. If he confused and sent value in BPS, not in percent, he might send 300 instead of 3. In that case, this `founderPct` will pass the validation of Line 88. And he will get all of first 100 token ids in Line 108 ~ 119.
After that, the while loop of `mint` will not break, and this leads out of gas revert. And mint and auction will not work.

## Recommended Mitigation Steps
Validate founder's `ownershipPct` in `_addFounders`
ERC721.transferFrom() would always revert
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/main/src/lib/token/ERC721.sol#L134


# Vulnerability details

## Impact
Due to the if statement in line 134 of ERC721 contract, the `transferFrom()` call would always revert. 

The if-statement would only pass when the owner of the token `operatorApprovals` to themself , however upon the call to _afterTokenTransfer , this would revert due to the check in ERC721Votes._moveDelegateVotes() that ensures there is no delegation to same from and to address.

## Proof of Concept
https://github.com/code-423n4/2022-09-nouns-builder/blob/main/src/lib/token/ERC721.sol#L134

## Tools Used
Manual review

## Recommended Mitigation Steps
It would be quite easier to use a require check similar in the OZ ERC71 library in https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L241
When totalSupply is low proposalThreshold() and quorum() can return zero
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/debe9b792cc70510eadf9b3728cde5b0f2ec9a1f/src/governance/governor/Governor.sol#L468
https://github.com/code-423n4/2022-09-nouns-builder/blob/debe9b792cc70510eadf9b3728cde5b0f2ec9a1f/src/governance/governor/Governor.sol#L475](https://github.com/code-423n4/2022-09-nouns-builder/blob/debe9b792cc70510eadf9b3728cde5b0f2ec9a1f/src/governance/governor/Governor.sol#L475


# Vulnerability details

### Proof of concept

```solidity
/// @notice The current number of votes required to submit a proposal
    function proposalThreshold() public view returns (uint256) {
        unchecked {
            return (settings.token.totalSupply() * settings.proposalThresholdBps) / 10_000;
        }
    }

    /// @notice The current number of votes required to be in favor of a proposal in order to reach quorum
    function quorum() public view returns (uint256) {
        unchecked {
            return (settings.token.totalSupply() * settings.quorumThresholdBps) / 10_000;
        }
    }
```

If `settings.token.totalSupply()` is still low, let’s say 50 and proposal and quorum thresholds are set below 2000 then both functions will return zero, essentially allowing any proposal to be submitted and reach quorum without even 1 token holder voting for it even though there were proposal and quorum thresholds set.

### Impact

The impact is that until there are enough tokens in total supply for the Governor contract, any proposal can be submitted, accepted and then after the delay it can gather quorum and be executed, all of this with zero votes for it. This can be a big concern for the DAO because pretty much any holder can execute any code.

### Recommendation

Make both functions return some sensible MIN_VALUE if they amount to zero, for example MIN_PROPOSAL_THRESHOLD = 50, MIN_QUORUM_THRESHOLD = 150.