xanoutas/solidity-security-findings huggingface dataset:
tx.origin may be removed in future and its usage is not recommended
Audit: 2023-07-nounsdao-findings
Severity: MEDIUM

# Lines of code

https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L1043


# Vulnerability details

## Impact
There is a chance that tx.origin will be removed from the Ethereum protocol in the future, so code that uses tx.origin  must be avoid using it.

There is also some EIPs being proposed for change/remove of tx.origin.

https://github.com/ethereum/EIPs/issues/637

https://www.reddit.com/r/ethereum/comments/6d11lv/erc_about_txorigin_change_for_account_abstraction/

In NounsDAOLogicV2.sol, 

```Solidity
File: nouns-contracts/contracts/governance/NounsDAOLogicV2.sol

1033    function _refundGas(uint256 startGas) internal {
1034        unchecked {
1035            uint256 balance = address(this).balance;
1036            if (balance == 0) {
1037                return;
1038            }
1039            uint256 basefee = min(block.basefee, MAX_REFUND_BASE_FEE);
1040            uint256 gasPrice = min(tx.gasprice, basefee + MAX_REFUND_PRIORITY_FEE);
1041            uint256 gasUsed = min(startGas - gasleft() + REFUND_BASE_GAS, MAX_REFUND_GAS_USED);
1042            uint256 refundAmount = min(gasPrice * gasUsed, balance);
1043            (bool refundSent, ) = tx.origin.call{ value: refundAmount }('');
1044            emit RefundableVote(tx.origin, refundAmount, refundSent);
1045        }
1046    }
```

Here, the function has used tx.origin as recipient to refund the amount but these can not work in future if the new EIPs concerning to remove tx.origin removal due to security issues got approved and tx.origin is removed from Ethereum protocol.

## Proof of Concept
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L1043

**For example:**
In NounsDAOV3Votes.sol, the same function logic has used msg.sender instead of tx.origin. The same should be considered in NounsDAOLogicV2.sol,

```Solidity
File: nouns-contracts/contracts/governance/NounsDAOV3Votes.sol

    function _refundGas(uint256 startGas) internal {
        unchecked {
            uint256 balance = address(this).balance;
            if (balance == 0) {
                return;
            }
            uint256 basefee = min(block.basefee, MAX_REFUND_BASE_FEE);
            uint256 gasPrice = min(tx.gasprice, basefee + MAX_REFUND_PRIORITY_FEE);
            uint256 gasUsed = min(startGas - gasleft() + REFUND_BASE_GAS, MAX_REFUND_GAS_USED);
            uint256 refundAmount = min(gasPrice * gasUsed, balance);
>>          (bool refundSent, ) = msg.sender.call{ value: refundAmount }('');
            emit RefundableVote(msg.sender, refundAmount, refundSent);
        }
    }
```

## Tools Used
Manual review

## Recommended Mitigation Steps
Use msg.sender instead of tx.origin.

```Solidity
File: nouns-contracts/contracts/governance/NounsDAOLogicV2.sol

    function _refundGas(uint256 startGas) internal {
The admin is a single point of failure and a centralization risk
Audit: 2023-07-nounsdao-findings
Severity: MEDIUM

# Lines of code

https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Admin.sol#L150
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L838


# Vulnerability details

## Impact


Having a single EOA as the only owner of contracts is a large centralization risk and a single point of failure. A single private key may be taken in a hack, or the sole holder of the key may become unable to retrieve the key when necessary. Consider changing to a multi-signature setup, or having a role-based authorization model.

A similar issue has been reported during a bot-race, but `onlyOwner` modifiers were detected.

Contract implements additional modifier: `onlyAdmin` which is also a single EOA.
Additionally, in `contracts/governance/NounsDAOLogicV2.sol`, there's explicitly check if `msg.sender == admin` for an administrative function.




## Proof of Concept

Basically, the whole `contracts/governance/NounsDAOV3Admin.sol` which contains multiple of administrative functions is using `onlyAdmin` modifier, which is single point of failure:

```
File: contracts/governance/NounsDAOV3Admin.sol

    modifier onlyAdmin(NounsDAOStorageV3.StorageV3 storage ds) {
        if (msg.sender != ds.admin) {
            revert AdminOnly();
        }
        _;
    }
```

Moreover, `contracts/governance/NounsDAOLogicV2.sol` defines function `_setPendingAdmin`, which explicitly checks if `msg.sender == admin`:

```
File: contracts/governance/NounsDAOLogicV2.sol

 function _setPendingAdmin(address newPendingAdmin) external {
        // Check caller = admin
        require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only');
```



## Tools Used

Manual code review

## Recommended Mitigation Steps

More than one user should be responsible for crucial features which might break or disable the contract.


## Assessed type

Other
cancelSig will not completely cancel signatures due to malleability vulnerabilities
Audit: 2023-07-nounsdao-findings
Severity: MEDIUM

# Lines of code

https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Proposals.sol#L270-L275
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Proposals.sol#L983


# Vulnerability details

## Impact

The current version of openzeppelin contracts has a high risk of vulnerability about signature malleability attack: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3610.
So if the signer only cancel one signature, the malicious proposer can still extend a fully valid signature through the previous signature to pass the proposal.

## Proof of Concept

```solidity
// CancelProposalBySigs.t.sol
contract TestSignatureMalleabilityAttack is ZeroState {
    function setUp() public virtual override {
        super.setUp();

        (signerWithVote, signerWithVotePK) = makeAddrAndKey('signerWithVote');

        vm.startPrank(minter);
        nounsToken.mint();
        nounsToken.transferFrom(minter, signerWithVote, 1);
        vm.roll(block.number + 1);
        vm.stopPrank();

        NounsDAOV3Proposals.ProposalTxs memory txs = makeTxs(makeAddr('target'), 0, '', '');
        uint256 expirationTimestamp = block.timestamp + 1234;
        NounsDAOStorageV3.ProposerSignature[] memory proposerSignatures = new NounsDAOStorageV3.ProposerSignature[](1);
        bytes memory signature = signProposal(proposer, signerWithVotePK, txs, 'description', expirationTimestamp, address(dao));
        vm.prank(signerWithVote);
        dao.cancelSig(signature);

        proposerSignatures[0] = NounsDAOStorageV3.ProposerSignature(
            signature,
            signerWithVote,
            expirationTimestamp
        );
        vm.expectRevert(abi.encodeWithSelector(NounsDAOV3Proposals.SignatureIsCancelled.selector));
        vm.prank(proposer);
        proposalId = dao.proposeBySigs(
            proposerSignatures,
            txs.targets,
            txs.values,
            txs.signatures,
            txs.calldatas,
            'description'
        );

        proposerSignatures[0] = NounsDAOStorageV3.ProposerSignature(
            to2098Format(signature),
            signerWithVote,
            expirationTimestamp
        );
        vm.prank(proposer);
        proposalId = dao.proposeBySigs(
            proposerSignatures,
            txs.targets,
            txs.values,
            txs.signatures,
            txs.calldatas,
            'description'
        );

        vm.roll(block.number + 1);

        assertEq(uint256(dao.state(proposalId)), uint256(NounsDAOStorageV3.ProposalState.Updatable));
    }

    // Copy from https://github.com/pcaversaccio/malleable-signatures/blob/1f618f556c0af48c44d27c7dbf1f97dc898ceda9/test/SignatureMalleability.t.sol#L78
    error InvalidSignatureLength();
    error InvalidSignatureSValue();
    function to2098Format(bytes memory signature) in
Functions will always return success even if it is failed
Audit: 2023-07-nounsdao-findings
Severity: MEDIUM

# Lines of code

https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/fork/newdao/NounsAuctionHouseFork.sol#L272-L275
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L818-L829


# Vulnerability details

## Impact
In NounsAuctionHouseFork.sol, _safeTransferETH() function is used to transfer ETH.

```Solidity
File: nouns-contracts/contracts/governance/fork/newdao/NounsAuctionHouseFork.sol

272    function _safeTransferETH(address to, uint256 value) internal returns (bool) {
273        (bool success, ) = to.call{ value: value, gas: 30_000 }(new bytes(0));
274        return success;
275    }
```

In NounsDAOLogicV2.sol, _withdraw() function is used to withdraw ETH.

```Solidity
File: nouns-contracts/contracts/governance/NounsDAOLogicV2.sol

818    function _withdraw() external returns (uint256, bool) {
819        if (msg.sender != admin) {
820            revert AdminOnly();
821        }
822
823        uint256 amount = address(this).balance;
824        (bool sent, ) = msg.sender.call{ value: amount }('');
825
826        emit Withdraw(amount, sent);
827
828        return (amount, sent);
829    }
```
In NounsDAOV3Admin.sol, _withdraw() function is used to withdraw ETH.

```Solidity
File: nouns-contracts/contracts/governance/NounsDAOV3Admin.sol

468    function _withdraw(NounsDAOStorageV3.StorageV3 storage ds) external onlyAdmin(ds) returns (uint256, bool) {
469        uint256 amount = address(this).balance;
470        (bool sent, ) = msg.sender.call{ value: amount }('');
471
472        emit Withdraw(amount, sent);
473
474        return (amount, sent);
475    }
```

The issue here is the above functions return success if the transfer was successful or not. It does not explicitly check for fail condition. 

By adding the require statement the contract will revert and throw an exception with the specified error message if the external call was not successful. This helps to ensure that the contract state remains consistent and avoids potential issues caused by failed executions.

**For example:**
Per solidity documentation,

In order to interface with contracts that do not adhere to the ABI, or to get more direct control over the encoding, the functions call, delegatecall and staticcall are provided. They all take a single bytes memory parameter and return the success condition (as a bool) and the returned data (bytes memory). The functions abi.encode, abi.encodePacked, abi.encodeWithSelector and abi.encodeWithSignature can be used to encode structured data.

```Solidity
bytes memory payload = abi.encodeWithSignature("register(string)", "MyName");
(bool success, bytes memory returnData) = address(nameReg).call(payload);
require(success);
```

## Proof of Concept
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contrac
ETH might stuck in NounsDAOProxy.sol
Audit: 2023-07-nounsdao-findings
Severity: MEDIUM

# Lines of code

https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOProxy.sol#L130-L132
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOProxy.sol#L138-L140


# Vulnerability details

## Impact
[NounsDAOProxy.fallback](https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOProxy.sol#L130C5-L132) and [NounsDAOProxy.receive](https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOProxy.sol#L138C5-L140) expect to receive ETH, but the contract and its implementation don't contain a function to withdraw the received ETH as [NounsDAOLogicV3._withdraw](https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV3.sol#L786C14-L788) and [NounsDAOLogicV2._withdraw](https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L818-L829C6)

## Proof of Concept
In [NounsDAOProxy.sol](https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOProxy.sol#L130-L140)
```solidity
    fallback() external payable {
        _fallback();
    }

    /**
     * @dev Fallback function that delegates calls to `implementation`. Will run if call data
     * is empty.
     */
    receive() external payable {
        _fallback();
    }
```

## Tools Used
VS
## Recommended Mitigation Steps
add `_withdraw` function as [NounsDAOLogicV3._withdraw](https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV3.sol#L786C14-L788) and [NounsDAOLogicV2._withdraw](https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L818-L829C6)





## Assessed type

ETH-Transfer
ds._proposals mapping is never updated when a new proposal is created via propose() in NounsDAOV3Proposals.sol 
Audit: 2023-07-nounsdao-findings
Severity: MEDIUM

# Lines of code

https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Proposals.sol#L160
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Proposals.sol#L175
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV3.sol#L189


# Vulnerability details

## Impact
in function propose(), after a new proposal is created, the `ds._proposals` variable in storage is never updated. Natspec comments for StorageV3 struct definition in NounsDAOInterfaces.sol  says that ds._proposals` is a mapping that contains references to all proposals ever made but propose() logic doesnt update the mapping with the new proposal. 


## Proof of Concept
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOV3Proposals.sol#L160-L186



In the above snippet we can see that ds._proposals mapping is not set here, only ds.latestProposalIds is. 

line 679 --->  https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOInterfaces.sol#L679 here says that _proposals is the offical record of all propsals ever submitted.

 Function propose() emits a new Proposal event without adding the new proposal to the offical record which is ds._proposals. 


Also in NounsDAOLogicV3.sol where library NounsDAOV3Proposals is used, _proposals is not updated in the function propose() ----> https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV3.sol#L189
## Tools Used
vs code 
## Recommended Mitigation Steps
add new proposal to ds._propsal before emiting new propsal event. 








## Assessed type

Error
Malicious user may force gas limit error on created proposals
Audit: 2023-07-nounsdao-findings
Severity: MEDIUM

# Lines of code

https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L245
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L305
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L343
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L372


# Vulnerability details

## Impact

User can create a proposal with arbitrary long array of targets. Function `cancel` iterates over those targets. Since this array may be long - the risk of out-of-gas error may occur - making the contract to be unable to cancel the proposal (as `cancel` would revert because of out of gas)

## Proof of Concept


While creating a proposal, user has control over list of `targets`. That list, provided by user, may be arbitrary long.
```
File: contracts/governance/NounsDAOLogicV2.sol

    function propose(
        address[] memory targets,
        uint256[] memory values,
        string[] memory signatures,
        bytes[] memory calldatas,
        string memory description
    ) public returns (uint256) {

    [...]
    newProposal.targets = targets;
```

This proposal may stuck and be un-removable, due to looping on unbounded array in function `cancel`:

```
File: contracts/governance/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
            );
        }
```

During the cancelation process, contract still needs to loop over unbounded array: `proposal.targets.length` whose length is controlled by malicious user (the one who created such a proposal). Trying to remove that proposal may always revert due to out of gas error.

The same issue (out of gas) may also occur while trying to execute `queue` or `execute` on that proposal.

```
   function queue(uint256 proposalId) external {
        require(
            state(proposalId) == ProposalState.Succeeded,
            'NounsDAO::queue: proposal can only be queued if it is succeede
Vetoer has too huge privileges which allow to burn veto power and change his/her address
Audit: 2023-07-nounsdao-findings
Severity: MEDIUM

# Lines of code

https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L876-L884
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOLogicV2.sol#L904-L906


# Vulnerability details

## Impact

Only a current user with veto power can either change vetoer or pernamently destroy veto power forever. Since this functions are crucial for governance, they should also be called by admin.
In the current scenario, vetoer has some higher privileges in comparison to admin (in a context of veto):
* Vetoer can set new vetoer (while admin cannot).
* Vetoer can destroy veto power forever (while admin cannot reverse that operation)


## Proof of Concept

```
File: contracts/governance/NounsDAOLogicV2.sol

 function _setPendingVetoer(address newPendingVetoer) public {
        if (msg.sender != vetoer) {
            revert VetoerOnly();
        }

        (...)

    function _burnVetoPower() public {
        // Check caller is vetoer
        require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');

```

While initializing the contract (`initialize`) admin sets the address of the vetoer. However, when vetoer is being set, he/she has more privileges (in the context of veto) then admin.
* Vetoer can set new vetoer (`_setPendingVetoer`). This function is only callable by vetoer. Admin cannot do anything about it, when vetoer decided to change its address for someone malicious.

* Vetoer can destroy veto power by calling `_burnVetoPower` and admin won't be able to reenable it. This is too high priv. for a vetoer. He/she should be able to only veto proposals, while, he/she can pernamently disable veto power.


## Tools Used

Manual code review

## Recommended Mitigation Steps

`_burnVetoPower` and `_setPendingVetoer` should be called by either current vetoer or current admin. 
Implement a function to reenable veto power (accesible by admin) - whenever malicious vetoer (e.g. due to having his/her account compromised) - disables it.
Implement a function which gives admin a priv. to change vetoer - whenever vetoer becomes malicious (e.g. due to having his/her account compromised).


## Assessed type

Access Control
Storage collision risk in NounsDAOProxy contracts
Audit: 2023-07-nounsdao-findings
Severity: MEDIUM

# Lines of code

https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOProxy.sol#L40
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOProxyV2.sol#L43
https://github.com/nounsDAO/nouns-monorepo/blob/718211e063d511eeda1084710f6a682955e80dcb/packages/nouns-contracts/contracts/governance/NounsDAOProxyV3.sol#L43


# Vulnerability details

## Impact
NounsDAOProxy contract may lose tracking its implementation address

## Proof of Concept

One of the main vulnerabilities of upgradeable contracts is storing the implementation address in the beginning slots. This address is later used by proxy for delegatecall to the implementation contract. Although the Compounds `GovernorBravoDelegator` contract stores the implementation address in its first three slots, the equivalent slot in the proxy may be changed silently in the future. The NounsDAOProxy contracts (V1-V3) are similar to `GovernorBravoDelegator` contract which has this potential risk. To reduce this risk, storing the implementation address in an arbitrary, far from beginning slots is recommended. ERC1967 is intended to mitigate this risk by storing the address in `implementation` slot which is defined as:

```
    bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
```

By storing the implementation address in the implementation slot of the proxy contract, the issue of storage collision disappears.


## Tools Used
Manual Review
## Recommended Mitigation Steps
Pretend to use OpenZeppelin's ERC1967 proxy


## Assessed type

Upgradable