xanoutas/solidity-security-findings huggingface dataset:
Vote delegation will fail if the ENS token holder is a contract wallet that isn’t ERC1155 compatible. 
Audit: 2023-10-ens-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L114
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L111


# Vulnerability details

## Impact
The `ERC20MultiDelegate` contract inherits the ERC1155 contract in order to track the balances of the token votes held by the delegates. Which helps in tracking the balances to be minted or burned. 

It’s implicitly assumed that the ENS tokens are airdropped to an EOA who then delegates votes to different targets. However, if the  ENS tokens belong to a smart contract wallet that isn’t an ERC1155 receiver, the smart contract wallet won’t be able to delegate its votes.
## Proof of Concept
To confirm this, create the following contract named ContractWalletand place it in the contracts folder. The contract wallet, will hold the ENS tokens, and will attempt to delegate votes to some targets. But it’s prevented from doing so as the transactions will revert with the error message ERC1155: transfer to non ERC1155Receiver implementer.

```solidity
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.2;

import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "./ERC20MultiDelegate.sol";

//A contract that's not ERC1155 compatible - missing onReceived function
contract ContractWallet{

    address public owner;
    ERC20Votes public token;
    ERC20MultiDelegate public multiDelegate;


    constructor(ERC20Votes _token, ERC20MultiDelegate _multiDelegate) {
        token = _token;
        multiDelegate = _multiDelegate;
        owner = msg.sender;
        console.log("TargetAttack:: deployed with owner = %s at address = %s, balance=%s", owner, address(this));
    }


  /**
    @dev This function returns the ENS token balance of the contract.
    @return ENSBalance is the ENS token balance of this contract 
     */
    function getBal() public view returns (uint256 ENSBalance) {
        ENSBalance = ERC20(address(token)).balanceOf(address(this));
    }

    /**
    @dev This function delegates votes by calling the ENSMultiDelegate contract
    @param _sources is the array of source addresses
    @param _targets is the array of target addresses
    @param _amounts is the array of the ENS Token amounts 
     */

    function delegateENS(uint256[] memory _sources,uint256[] memory _targets, uint256[] memory _amounts) external {
        multiDelegate.delegateMulti(_sources, _targets, _amounts);
    }


    /**
    @dev This function approves the ENS Multidelegate contract to spend the ContractWallet's tokens
     */
    function approve() public returns (bool success) {
        success = token.approve(address(multiDelegate), token.balanceOf(address(this)));

    }
}

```
Include the following test in the describe block that tests the deposits in delegateMulti.js

```hardhat

describe("deposit", () => {
it.("Au
User scan steal other users funds when using ERC20 tokens with fees on transfer
Audit: 2023-10-ens-findings
Severity: HIGH

# Lines of code

Mint of ERC1155 tokens that doesn't take into account fees on transfer of the ERC20 tokens: https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L114


# Vulnerability details

## Impact
User scan steal other users funds when using ERC20 tokens with fees on transfer since the ERC1155 tokens are minted without checking this.
## Proof of Concept
If we use a ERC20 token with a 10% of fees on transfer in the contract and a user called Deployer delegates 100 tokens to Alice, only 90 tokens will be sent to the contract, since the other 10 were paid as fees. Nonetheless, Deployer still gets 100 ERC1155 tokens, since the mint of them doesn't take into account the fee paid in the ERC20 token. Now, another user called Ema delegates 200 tokens to Alice, so the contract balance of Alice's ERC20ProxyDelegator will be 90 + 180 = 270. Deployer wants to reimburse his tokens from Alice, and as his ERC1155 balance is 100, he is able to get 90 tokens back, since another 10 were paid as fees. Now Deployer has 90 tokens, eventhough he should have 81. Now Ema tries to reimburse the 180 tokens that correspond to her, but the contract only holds 170 tokens. Deployer user 10 tokens of Ema for paying for the fees of his transaction.

Here I add a test that represents the example I explained. I created a MockTokenWithFees which is the same as ENSToken contract, but there is no mint to the Deployer in the constructor and with the ```transferFrom``` override like this.

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        uint256 feeForTransfer = (amount * 1000) / 10_000;
        _transfer(from, to, (amount - feeForTransfer));
        // Random address, let's it is the "Treasury", a dead address or whatever
        _transfer(from, 0xdAC17F958D2ee523a2206206994597C13D831ec7, feeForTransfer);
        return true;
    }


This will allow the Deployer to use Ema's tokens for paying for his transfer fees, making her reimbursement revert. The test can be added to /test/delegatemulti.js, but the token used should be the MockTokenWithFees instead of the ENSToken and the amount minted to the Deployer in the beforeEach should be just 100 tokens. It contains some comments in order to make it easier to follow the flow:

    it('PoC fees on transfer', async () => {
      const delegatorTokenAmount = await token.balanceOf(deployer);
      // const customAmount = ethers.utils.parseEther('10000000.0'); // ens

      // give allowance to multi delegate contract
      await token.approve(multiDelegate.address, delegatorTokenAmount);
      // delegate multiple delegates
      const delegates = [alice];
      const amounts = delegates.map(() =>
        delegatorTokenAmount.div(delegates.length)
      );

      await multiDelegate.delegateMu
Unbounded Loops In _delegateMulti() can lead to Denail of Service
Audit: 2023-10-ens-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L85
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L124
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L173
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L163
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L144
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L155


# Vulnerability details

## Impact
Several function calls are made in unbounded loops that can lead to denial of service

## Proof of Concept
```
function _delegateMulti(
        uint256[] calldata sources,
        uint256[] calldata targets,
        uint256[] calldata amounts
    ) internal {
        uint256 sourcesLength = sources.length;
        uint256 targetsLength = targets.length;
        uint256 amountsLength = amounts.length;

        require(
            sourcesLength > 0 || targetsLength > 0,
            "Delegate: You should provide at least one source or one target delegate"
        );

        require(
            Math.max(sourcesLength, targetsLength) == amountsLength,
            "Delegate: The number of amounts must be equal to the greater of the number of sources or targets"
        );

        // Iterate until all source and target delegates have been processed.
        for (
            uint transferIndex = 0;
            transferIndex < Math.max(sourcesLength, targetsLength);
            transferIndex++
        ) {
            address source = transferIndex < sourcesLength
                ? address(uint160(sources[transferIndex]))
                : address(0);
            address target = transferIndex < targetsLength
                ? address(uint160(targets[transferIndex]))
                : address(0);
            uint256 amount = amounts[transferIndex];

            if (transferIndex < Math.min(sourcesLength, targetsLength)) {
                // Process the delegation transfer between the current source and target delegate pair.
                _processDelegation(source, target, amount);
            } else if (transferIndex < sourcesLength) {
                // Handle any remaining source amounts after the transfer process.
                _reimburse(source, amount);
            } else if (transferIndex < targetsLength) {
                // Handle any remaining target amounts after the transfer process.
                createProxyDelegatorAndTransfer(target, amount);
            }
        }

        if (sourcesLength > 0) {
            _burnBatch(msg.sender, sources, amounts[:sourcesLength]);
        }
        if (targetsLength > 0) {
            _mintBatch(msg.s
Potential loss of funds in `createProxyDelegatorAndTransfer`
Audit: 2023-10-ens-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L155-L161
https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L173-L190


# Vulnerability details

## Impact

The `deployProxyDelegatorIfNeeded` doesn't save the address of the newly deployed proxy delegator and so when `createProxyDelegatorAndTransfer` is called and the target doesn't already have a proxy, all the token amount will be burned (transferred to `address(0)`) and the caller will not be able to get his tokens back.

## Proof of Concept

The issue occurs in the `deployProxyDelegatorIfNeeded` function below :

```solidity
function deployProxyDelegatorIfNeeded(
    address delegate
) internal returns (address) {
    address proxyAddress = retrieveProxyContractAddress(token, delegate);

    // check if the proxy contract has already been deployed
    uint bytecodeSize;
    assembly {
        bytecodeSize := extcodesize(proxyAddress)
    }

    // if the proxy contract has not been deployed, deploy it
    if (bytecodeSize == 0) {
        //@audit doesn't save into proxyAddress meaning will return proxyAddress == address(0)
        new ERC20ProxyDelegator{salt: 0}(token, delegate);
        emit ProxyDeployed(delegate, proxyAddress);
    }
    return proxyAddress;
}
```

As you can see when the function check if the delegate already have a proxy by fetching the address `proxyAddress` and if this address is zero (equivalent to `bytecodeSize == 0`), it will create a new `ERC20ProxyDelegator` contract, but the issue is that the address of the new contract is not saved to `proxyAddress` and so after deploying the new proxy the function will return the old `proxyAddress` value which was `address(0)`.

This will cause a loss of funds when `deployProxyDelegatorIfNeeded` is called in the `createProxyDelegatorAndTransfer` function below :

```solidity
function createProxyDelegatorAndTransfer(
    address target,
    uint256 amount
) internal {
    address proxyAddress = deployProxyDelegatorIfNeeded(target);
    //@audit tokens will be burned if proxyAddress == address(0)
    token.transferFrom(msg.sender, proxyAddress, amount);
}
```

If the `target` doesn't already have a proxy, `deployProxyDelegatorIfNeeded` will return `address(0)` as explained above and thus all the caller tokens will be burned and he will not be able to get them back.

## Tools Used

Manual review

## Recommended Mitigation Steps

Store the address of the newly created proxy delegator in `deployProxyDelegatorIfNeeded` :

```solidity
function deployProxyDelegatorIfNeeded(
    address delegate
) internal returns (address) {
    address proxyAddress = retrieveProxyContractAddress(token, delegate);

    // check if the proxy contract has already been deployed
    uint bytecodeSize;
    assembly {
        bytecodeSize := extcodesize(proxyAddress)
    }

    // if the proxy contract has not been deployed, deploy it
    if (bytecodeSize == 0) {
Any arbitrary user can call delegateMulti() and pass in any source addresses, target addresses, and amounts to transfer voting power
Audit: 2023-10-ens-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L57-L117


# Vulnerability details

## Impact
Any arbitrary user can call delegateMulti() and pass in any source addresses, target addresses, and amounts to transfer voting power without legitimate approval.

## Proof of Concept
The key issue is that delegateMulti() is a public function that can be called by anyone. It does not have any access control or authentication checks.

There is no verification that the caller actually owns or has approval for the source voting tokens.

So in its current form, delegateMulti() allows anyone to freely transfer voting power between accounts, including draining voting power from accounts.

## Tools Used
Manual review

## Recommended Mitigation Steps
Include access control on who can transfer voting power.


## Assessed type

Access Control
Can not update delegated votes of a created `ERC20ProxyDelegator`
Audit: 2023-10-ens-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L18


# Vulnerability details

## Impact
The delegated votes of a deployed `ERC20ProxyDelegator` contract cannot be increased, resulting in methods `_processDelegation` and `createProxyDelegatorAndTransfer` broken.

## Proof of Concept
`ERC20ProxyDelegator` uses function `delegate()` from OpenZepplin's `ERC20Votes.sol`, which is declared as the code block below:

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/governance/utils/Votes.sol#L134-L137

    function delegate(address delegatee) public virtual {
        address account = _msgSender();
        _delegate(account, delegatee);
    }

    function _delegate(address account, address delegatee) internal virtual {
        address oldDelegate = delegates(account);
        _delegatee[account] = delegatee;

        emit DelegateChanged(account, oldDelegate, delegatee);
        _moveDelegateVotes(oldDelegate, delegatee, _getVotingUnits(account));
    }

    function _moveDelegateVotes(address from, address to, uint256 amount) private {
        if (from != to && amount > 0) {
            if (from != address(0)) {
                (uint256 oldValue, uint256 newValue) = _push(
                    _delegateCheckpoints[from],
                    _subtract,
                    SafeCast.toUint208(amount)
                );
                emit DelegateVotesChanged(from, oldValue, newValue);
            }
            if (to != address(0)) {
                (uint256 oldValue, uint256 newValue) = _push(
                    _delegateCheckpoints[to],
                    _add,
                    SafeCast.toUint208(amount)
                );
                emit DelegateVotesChanged(to, oldValue, newValue);
            }
        }
    }

It is worth noting that although ERC20Votes guarantees that one account only has one delegatee, there are no other functions preset in `ERC20Votes` that can update delegate status. As the included balance can only be updated by calling `delegate` again, simply changing token balance in the proxy after it is deployed cannot change the delegated votes. 

## Tools Used
mannual review

## Recommended Mitigation Steps
call `_token.delegate(_delegate)` when the balance changes in each proxy.

##Note for judge
This vulnerability can be fixed by changing the transferfrom logic of the ERC20Vote token. However, the sponsor mentioned that the audit should consider any ERC20-compliant token that implements ERC20Votes, so I think this is a valid high risk finding.





## Assessed type

Error
Use of "else if" chain in _delegateMulti function will cause a subsequent condition not to be checked
Audit: 2023-10-ens-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L98-L108


# Vulnerability details

## Impact
Remaining source amounts and target amounts won't be handled if "(transferIndex < Math.min(sourcesLength, targetsLength))" is true.

## Proof of Concept
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L98-L108

The issue lies in the use of "else if" chain. If "(transferIndex < Math.min(sourcesLength, targetsLength))" is true, automatically the code will stop and not check any other conditions after it.

Whereas, there are two other important conditions after the first if statement:

 else if (transferIndex < sourcesLength) {
                // Handle any remaining source amounts after the transfer process.
                _reimburse(source, amount);
            } else if (transferIndex < targetsLength) {
                // Handle any remaining target amounts after the transfer process.
                createProxyDelegatorAndTransfer(target, amount);

The vulnerability is the use of the "else if" chain. What this means is that it is only when the first "if" statement is false that the first "else if" will be checked. 

And if the first "else if" condition is true, the code won't check the second "else if" condition.

Until the first "else if" condition fails that the code will check the second "else if" condition.


## Tools Used
Manual review

## Recommended Mitigation Steps
Use "if" instead of "else if"



## Assessed type

Context
Relying solely on the _delegate address to generate the proxy contract address is vulnerable
Audit: 2023-10-ens-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L198-L216


# Vulnerability details

## Impact
• Funds could bewithdrawn/transferred by the wrong user
• Votes would be cast on behalf of both users together rather than separately


## Proof of Concept
**The retrieveProxyContractAddress() function is computing the address where the ERC20ProxyDelegator contract will be deployed. It is using the create2 opcode to deterministically compute the address based on:**
**• The address of the ERC20MultiDelegate contract (this)**
**• A salt value (hard-coded to 0)**
**• The keccak256 hash of the ERC20ProxyDelegator deployment bytecode**
**By using a constant 0 salt each time, it means the same proxy address will be computed for a given token and delegate.**
**For example:**

      function retrieveProxyContractAddress(
             ERC20Votes _token,
             address _delegate
         ) private view returns (address) {

             // Same salt of 0 used each time
             uint256 salt = 0;

             bytes32 hash = keccak256(
                 abi.encodePacked(
                     bytes1(0xff),
                     address(this),
                     salt, 
                     keccak256(ERC20ProxyDelegator bytecode)
                 )
             );

             return address(uint160(uint256(hash)));
     }
**This is problematic because each proxy contract will essentially override the previous one deployed at that address. Only the most recently deployed proxy will be active.**
**It also means the number of tokens delegated will not accurately match the number of minted ERC1155 tokens, since reusing the same proxy address means delegating multiple times to the same address.**



## Tools Used
Manual
## Recommended Mitigation Steps
**A probable suggestion to fix this, the salt should be unique per delegation, like an incrementing nonce:**

     // Storage
     uint256 public nextSalt;

     function retrieveProxyContractAddress(
             ERC20Votes _token,
             address _delegate
         ) private returns (address) {

             uint256 salt = nextSalt;
             nextSalt++;
        
             // Rest of function unchanged

             return address(uint160(uint256(hash))); 
     }

**Now each call will deploy a new proxy contract address and maintain the 1:1 relationship between number of tokens delegated and minted.**




## Assessed type

Other
use higher version of openzeppelin library instead of vulnerible ones.
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/package.json#L14-L15


# Vulnerability details

## Impact
the Op lib has some dangerous vulnerabilities in lower versions especially when you work with ERC1155 

Openzeppelin already says the lower versions are vulnerable. 

Affected versions

`>= 4.2.0 < 4.3.3`

Patched versions

`4.3.3`

look at this https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-wmpv-c2jp-j2xg
## Proof of Concept
```json 
  "author": "ENS Team (@ensdomains)",
  "license": "MIT",
  "dependencies": {
    "@ensdomains/ens-contracts": "^0.0.7",
    "@openzeppelin/contracts": "^4.3.1",
    "keccak256": "^1.0.3"
  },
```
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/package.json#L14-L15
## Tools Used
vs code 
## Recommended Mitigation Steps
upgrade versions to 4.3.3


## Assessed type

Other
Zero address Salt used in Creating delegate contract
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L15
https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L210


# Vulnerability details

## Impact
A zero-valued salt used, means an attacker can predict the contract address of a new delegate, this can lead an attacker to create the `ERC20ProxyDelegator` on behalf of the user, this is to allow for max access to the tokens to be transfered

## Proof of Concept
```solidity
contract ERC20ProxyDelegator {
    constructor(ERC20Votes _token, address _delegate) {
        _token.approve(msg.sender, type(uint256).max);
        _token.delegate(_delegate);
    }
}
```
Starting from this contract, we see anyone can generate an ERC20ProxyDelegator using tokenaddress and delegate. One missing thing was the salt used. We also see the predicted address can be calculated in the function at https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L198

A malicious user, can go ahead by frontrunning or getting delegate address ahead to call the line https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L186 , this would give the attacker unlimited approvals and anytime the real user calls, it just returned the contract address as it already exists as shown here https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L185 - L189.

This allows attacker to transfer any vote token from the delegate if new tokens are transferred to the `ERC20ProxyDelegator` contract

## Tools Used
Manual Review
## Recommended Mitigation Steps
Randomize the salt and add an access control to force the delegate contract only to create `ERC20ProxyDelegator` contract





## Assessed type

Access Control