xanoutas/solidity-security-findings huggingface dataset:
ENS (ERC20Votes) token transferred to the proxy contract will be lost forever.
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L15-L20
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L110-L112


# Vulnerability details

## Impact
ENS (ERC20Votes) token transferred to the proxy contract will be lost forever. As delegation amount is out of sync with the ERC1155 balance if the ENS token is transferred to the proxy contract.

## Proof of Concept

When the user delegates 100 tokens to A, the ERC1155 balance will be 100.

After that, somebody transfers 20 tokens to the proxy contract. Result in 120 tokens actually being delegated but ERC1155 balance remains 100.

```solidity
        if (sourcesLength > 0) {
            _burnBatch(msg.sender, sources, amounts[:sourcesLength]);
        }
```

When the user tries to undelegate 120 tokens, it will call _burnBatch to burn the ERC1155 token. However, only 100 ERC1155 token exists. So, it can't burn 120 tokens resulting in a revert.

## Tools Used

Manual Review

## Recommended Mitigation Steps

If burn more than balance should be clamped to the balance. It shouldn't cause a problem as _reimburse has already withdrawn that amount to the user's wallet.





## Assessed type

Token-Transfer
OpenZeppelin 's recent v5.0.0 release includes many changes to libraries/contracts that can directly affect ERC20MultiDelegate functionality
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
OpenZeppelin just released v5.0.0, and there happen to be several significant changes that directly affect the **ERC20MultiDelegate** implementation, directly and indirectly, via packages used and inherited from.

Here’s a link to the release: https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.0.0

Below is a summary of some of the highlights of some removals/changes in v5.0.0 that I’ve compiled that I would suggest changes be made ASAP to account for these updates to relevant OZ libraries to avoid potentially serious issues with security and expected contract functionality. These include changes made to inherited contracts and their parents and changes to contracts that are directly and/or indirectly utilized or referenced within **ERC20MultiDelegate**:

ERC20Votes: Changed internal vote accounting to reusable Votes module previously used by ERC721Votes. Removed implicit ERC20Permit inheritance. Note that the DOMAIN_SEPARATOR getter was previously guaranteed to be available for ERC20Votes contracts, but is no longer available unless ERC20Permit is explicitly used; ERC-5267 support is included in ERC20Votes with EIP712 and is recommended as an alternative. (#3816)

ERC1155: Deleted _beforeTokenTransfer and _afterTokenTransfer hooks, added a new internal _update function for customizations, and refactored all extensions using those hooks to use _update instead. (#3838, #3876, #4377)

ERC1155: Removed check for address zero in balanceOf. (#4263)

ERC1155Receiver: Removed in favor of ERC1155Holder. (#4450) -> **ERC20MultiDelegate** utilizes IERC1155Receiver

## Proof of Concept
N/A

## Tools Used
Solidity

## Recommended Mitigation Steps
Address the above updates to OZ libraries/contracts one by one, by accordingly making updates in code to account for contracts/libraries/functions/variables that were removed/changed/updated in OpenZeppelin v5.0.0 release.


## Assessed type

Library
Approvals to ERC20MultiDelegate from ERC20ProxyDelegators can be depleted by a malicious actor
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
`ERC20MultiDelegate` deploys an `ERC20ProxyDelegator` contract for a given delegate address if it does not exist, which approves the maximum uint256 value to the `ERC20MultiDelegate ` in the constructor in [ERC20MultiDelegate.sol#L17](https://github.com/code-423n4/2023-10-ens/blob/main/contracts/ERC20MultiDelegate.sol#L17). This is the only time tokens are approved from the `ERC20ProxyDelegator` and once the allowance has run out, no more transfers out are possible.

This is nearly impossible to be a problem considering the max supply of the ENS token is 100000000000000000000000000 but more generally this may be an exploitable issue if it is used with tokens with a larger supply. Potentially this could lead to stuck tokens, which the `ERC20MultiDelegate` is not able to withdraw from the `ERC20ProxyDelegator` once the approvals are depleted and could be exploited by a malicious user causing a denial of service for another user by depleting the approvals for somebody elses proxy, or by creating the proxy for somebody elses address and depleting approvals before they've ever used it.
## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Allow `ERC20MultiDelegate` to transfer tokens from `ERC20ProxyDelegator` without approval through the use of an onlyOwner function inside `ERC20ProxyDelegator` which makes the transfer.


## Assessed type

DoS
ERC20MultiDelegate._processDelegation makes a wrong assumption that an ERC20ProxyDelegator instance for source will always exist
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
In the **delegateMulti** flow, when the **_processDelegation** logic path is followed, there is a wrong assumption that there already exists an instance of **ERC20ProxyDelegator** for the source address passed to **_processDelegation**. 

## Proof of Concept
Let’s say some privileged actor is able to delete and/or disable an existing **ERC20ProxyDelegator** instance for source that’s already been deployed via the **deployProxyDelegatorIfNeeded** function, at some previous time. I’ve wrote this function to roughly simulate the logic flow in such a case where this was done:
```
function transferBetweenDelegatorsBAD(
   address from, 
   address to, 
   uint256 amount
) internal { 
   address proxyAddressFrom = address(0); 
   address proxyAddressTo = retrieveProxyContractAddress(token, to); 
   token.transferFrom(proxyAddressFrom, proxyAddressTo, amount); 
}
```

I’ve replaced all calls to **transferBetweenDelegators** within **ERC20MultiDelegate** with my **transferBetweenDelegatorsBAD** function to simulate contract behavior in the event that an **ERC20ProxyDelegator** instance for source was removed, disabled, or never existed to start with. 

In this simulated case that mirrors real-world contract behavior given such a case, the following re-deposit test cases would fail:
**should be able to re-delegate multiple delegates in behalf of user (1:1)**
**should be able to re-delegate multiple delegates in behalf of user (many:many)**
**should be able to re-delegate to already delegated delegates**

With the following error:
**Error: VM Exception while processing transaction: reverted with reason string 'ERC20: insufficient allowance'**

This essentially poses intentional and/or unintentional **centralization risks**, where privileged actors like ENS deployers who may deploy and/or own **ERC20ProxyDelegator** instances can delete or swap out for different contract instances or otherwise disable such instances which can lead to errors, unexpected functionality, invalid delegation changes, or even security risks.

## Tools Used
Solidity/JS/Hardhat

## Recommended Mitigation Steps
Within **_processDelegation**, invoke **deployProxyDelegatorIfNeeded(source)** (in the same way it’s done for target), before invoking **transferBetweenDelegators(source, target, amount)**. The gas savings of not doing so is not worth the risk of the possibility of an ERC20ProxyDelegator contract not existing for the source address and the potential errors/vulnerabilities that could arise from such a case. 


## Assessed type

Token-Transfer
use of integer type for salt at the point of contract deployment can lead to malformed proxy delegator contracts
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
integer type is used for ```salt``` when deploying Proxy Delegator and this can lead to malformed proxy delegators and loss of vote.

## Proof of Concept
When ```deployProxyDelegatorIfNeeded(...)``` is called to deploy a proxy if the delegate does not exist, the contract is deployed with salt as an integer.

```solidity
function deployProxyDelegatorIfNeeded(
        address delegate
    ) internal returns (address) {
        ...
        if (bytecodeSize == 0) {
            // @audit salt is an integer instead of bytes
            new ERC20ProxyDelegator{salt: 0}(token, delegate);
            emit ProxyDeployed(delegate, proxyAddress);
        }
        ...
    }
```
This is contrary to the [EIP 1014](https://eips.ethereum.org/EIPS/eip-1014#specification) standard which explicitly states that "_salt is always 32 bytes (a stack item)_" and it can lead to malformed proxy delegator contracts leading to delegators loosing their delegated votes.

## Tools Used
Manual review

## Recommended Mitigation Steps
Consider rewriting the deployProxyDelegatorIfNeeded(...) as shown below
```solidity
function deployProxyDelegatorIfNeeded(
        address delegate
    ) internal returns (address) {

        ...
        if (bytecodeSize == 0) {
            // @audit convert salt to byte
            new ERC20ProxyDelegator{salt: bytes(0)}(token, delegate);
            ...
        }
        return proxyAddress;
    }
```


## Assessed type

Error
Failure to mint NFTs for new delegates may lead to underflows.
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L124-L129
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L192-L196
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L131


# Vulnerability details

## Impact

The ERC20MultiDelegate.sol contract relies on the `balanceOf` ERC1155 function to determine balances for each delegate. However, this does not account for any delegates that have not yet been minted an NFT. This could cause an underflow when transferring tokens.

## Proof of Concept

Here is the potential issue in how delegate balances are determined in the `_processDelegation()` function.

**[ERC20MultiDelegate.sol#_processDelegation()](https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L124-L129)**

**[ERC20MultiDelegate.sol#getBalanceForDelegate](https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L192-L196)**

```solidity
function _processDelegation(
  address source,
  address target,
  uint256 amount
) internal {

  uint256 balance = getBalanceForDelegate(source);

  // ... transfer logic
}

function getBalanceForDelegate(address delegate) 
  internal view returns (uint256) 
{
  return ERC1155(this).balanceOf(msg.sender, uint256(uint160(delegate)));
}
```

Specifically, `getBalanceForDelegate()` relies on the ERC1155 `balanceOf()` to check the delegate's balance. 

However, this will return 0 if no NFT has been minted yet for that delegate.

Then on line 131, there is no check that **[amount <= balance](https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L131)** when `balance` could be 0 incorrectly.

This could cause an underflow and invalid transfer when sending from a delegate with no NFT.


Here is an example that would be very helpful here. Let me walk through one:

```solidity

contract Vulnerable {
  function delegate(address delegate, uint amount) external {

    uint balance = ERC1155(this).balanceOf(msg.sender, delegate);
    
    // balance will be 0 if no NFT minted!

    require(amount <= balance, "Insufficient balance");

    // transfer tokens

  }
}

contract Attacker {
  function exploit(Vulnerable v) external {
  
    address delegate = 0x123; // no NFT minted yet

    uint amount = 100;

    v.delegate(delegate, amount); // underflow!

  }
}
```

Here's what happens in this example:

1. Vulnerable contract checks delegate's balance via ERC1155 `balanceOf()`.

2. For a new delegate, `balanceOf()` will be 0 

3. Require check passes since 100 <= 0 (underflow)

4. Transfer allowed to new delegate with 0 balance

This shows how relying solely on `balanceOf()` enables an underflow when no
Missing revocation function risks users' locked delegations to malicious actors.
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

There is no way to revoke delegations or withdraw tokens once delegated. Users may want an escape hatch if a delegate turns malicious.

## Proof of Concept

The contract is missing functionality for users to revoke delegations if needed. 

The issue is that once tokens are delegated via the **[delegateMulti() function](https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L57-L63)**, there is no way for the user to withdraw or revoke that delegation.

The concerning code is in the `ERC20ProxyDelegator` contract deployed by `delegateMulti()`: **[ERC20MultiDelegate.sol#ERC20ProxyDelegator](https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L16-L19)**

```solidity
// ERC20ProxyDelegator.sol

constructor(ERC20Votes _token, address _delegate) {
  _token.approve(_delegate, uint(-1));
  _token.delegate(_delegate); 
}
```

On lines 17-18, the delegation is approved with a max uint value and delegated to the target. This proxy contract has no functions to revoke approval or withdraw delegation.

Since the problem is, that there are **no functions to revoke this delegation or withdraw it back to the user.** 

This means if a malicious delegate emerges later, users have no way to get their voting power back!

Some examples of risks this introduces:

- Delegates could act against users' interests if they turn malicious.
- Users are locked into delegations, even if delegates change their behavior.
- No escape hatch if a delegate is hacked or sells out.

Ideally, there should be a `revokeDelegation` function like.

```solidity
function revokeDelegation() external {
   token.revokeApproval(delegate);
   token.delegate(msg.sender); 
}
```

This would allow users to take back control of their voting power if needed.

Without any revocation functionality, users are fully dependent on delegates acting honestly and benevolently in perpetuity. This complete lack of recourse is highly dangerous in the case delegates turn malicious for any reason.

He's an example of proof here that will help substantiate this vulnerability. Here is a demonstration:

```solidity

contract Vulnerable {
  function delegate(address delegate) external {
    // approve and delegate voting power
    ERC20(token).approve(delegate, uint(-1));
    ERC20(token).delegate(delegate);
  }
}

// No revokeDelegation function!

contract Attacker {
  function exploit(address user) external {
    Vulnerable(vuln).delegate(attackerAddress); 

    // user now locked into delegating to attackerAddress  
    // No way to revoke due to missing function

  }
}
```

Thi
Redelegating each time is inefficient; separate delegation for better gas usage.
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The proxy contract delegate call re-delegates every time. It may be better to delegate only once, and update when needed. This would save gas costs.

## Proof of Concept

The proxy contract created in `deployProxyDelegatorIfNeeded()` does re-delegate every time it is called, which is inefficient.

The concerning code is in **[ERC20ProxyDelegator.sol#contract ERC20ProxyDelegator](https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L15-L19)**

```solidity
// ERC20ProxyDelegator.sol

constructor(ERC20Votes _token, address _delegate) {
  _token.approve(_delegate, type(uint256).max);
  _token.delegate(_delegate); 
}
```

On line 18, it calls **[_token.delegate(_delegate)](https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L18)** to re-delegate to the target delegate every time this contract is invoked.

A better approach would be to delegate only once in the constructor:

```solidity
constructor(ERC20Votes _token, address _delegate) {
  _token.approve(_delegate, type(uint256).max);
  
  // Delegate only once
  _token.delegate(_delegate);
}
```

And then provide a separate `redelegate()` function to call when needed:

```solidity 
function redelegate(address newDelegate) external {
  _token.delegate(newDelegate);
}
```

This would save gas by delegating only once until an update is required. The current re-delegating on each call is wasteful if the delegate isn't changing.

The continual re-delegation in the proxy contract is inefficient and waisteful. Let me provide an in-depth explanation:

The core issue is in the `ERC20ProxyDelegator` contract created by `deployProxyDelegatorIfNeeded()`:

```solidity
constructor(ERC20Votes token, address delegate) {
  token.approve(delegate, uint(-1));

  // Re-delegates every time!
  token.delegate(delegate); 
}
```

Calling `token.delegate(delegate)` re-delegates the voting power to the `delegate` on every contract call.

This is wasteful for a couple of reasons:

- The delegate isn't changing in most cases
- But gas is still spent to re-delegate each time

A better approach is to separate the one-time delegation from potential re-delegations:

```solidity
// Constructor - single initial delegation
constructor(ERC20Votes token, address delegate) {
  token.approve(delegate, uint(-1));
  token.delegate(delegate);
}

// Redelegate function - called only when needed
function redelegate(address newDelegate) external {
   token.delegate(newDelegate);
}
```

Here the initial delegation happens once. Re-delegation is gated behind a separate function.

This improves efficiency by:
Owner's setUri ability allows pointing to malicious metadata. Restrict for safety.
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The `setUri` function allows the owner to update the URI for all NFT token IDs. This could be abused to point to malicious metadata. 
Consider restricting this to only allow updating unpublished token IDs.

## Proof of Concept

**[ERC20MultiDelegate.sol#function setUri](https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L151-L153)**

```solidity
function setUri(string memory uri) external onlyOwner {
  _setURI(uri);
}
```

This setUri function is using the `_setURI` internal function from the ERC1155 standard to set the base URI for all token IDs. By making it only callable by the owner, the owner can update the URI for all tokens, even ones that have already been minted. 

This could be abused by pointing it to malicious metadata JSON that tricks users. For example by setting the name, image, or description to appear like a legitimate NFT from the collection.

Let me provide a more in-depth explanation of the permission issue with the `setUri` function:

The `setUri` function allows the contract owner to set the base URI for all NFT tokens minted by this ERC1155 contract. 

Here is the full context of the setUri function:

```solidity
// ERC1155.sol (imported by this contract)
function _setURI(string memory newuri) internal virtual {
    uri = newuri;
}

// This contract
function setUri(string memory uri) external onlyOwner {
  _setURI(uri); 
}
```

When an NFT is minted, its metadata URI is typically formed by concatenating the base `URI + tokenId`. 

For example, if the base URI is set to "https://example.com/" and tokenId is 1, the resulting metadata URI will be "https://example.com/1".

This allows the contract to dynamically pull metadata for each token ID from a centralized source.

The issue is that `setUri` allows the owner to change this base URI at any time after deployment. This means they can point it to a malicious DNS server under their control.

For example, the owner could set the URI to "https://malicious.com/". Now if anyone views token ID 1, they would unknowingly be visiting "https://malicious.com/1" which could contain phishing content designed to steal funds.

By allowing the owner to update the URI even for already minted tokens, it puts trust in the owner not to abuse this power. 

A better approach would be to only allow the URI to be set once, or to only allow updating it for unpublished token IDs.

This prevents the owner from hijacking the metadata for existing tokens already in circulation, limiting the potential for abuse.

Here's proof of the issue through a code example for credibility, to demonstrate how the `setUri` function can be abused:

```solidity
// Example exploit contract 

interface ERC1155 {
  function setUri(string calldata uri) external;
}

contract