xanoutas/solidity-security-findings huggingface dataset:
Same multiple delegate values result in wrong calculation of delegated votes
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

If by mistake same delegate value is given multiple times to targets array then delegation of votes is wrongly calculated.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

In test related to delegating multiple delegates in behalf of user. We have only 2 delegate values deployer and alice but as alice is added to delegates 3 times (Which can happen in case of large array in which same value can be given more than ones by mistake) deployer votes become 1/4 th of total votes and alice gets very large number of votes although They should have equal and 1/2 of total votes respectively.


      describe('deposit', () => {
      it('should be able to delegate multiple delegates in behalf of user', 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 = [deployer, alice, alice, alice];
      const amounts = delegates.map(() =>
        delegatorTokenAmount.div(delegates.length)
      );

      await multiDelegate.delegateMulti([], delegates, amounts);

      const delegatorTokenAmountAfter = await token.balanceOf(deployer);
      expect(delegatorTokenAmountAfter.toString()).to.equal('0');

      // delegator must have 1/4 of the votes the delegator delegated
      const votesOfDelegator = await token.getVotes(deployer);
      expect(votesOfDelegator.toString()).to.equal(
        delegatorTokenAmount.div(4).toString()
      );

## Tools Used
Manually

## Recommended Mitigation Steps
Checks can be applied to avoid duplicate values.


## Assessed type

Error
amount could still have some dust left
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


## Impact
Amount can still remain after transfer process that can lead unbalancing of length between ```sources```, ```targets``` and ```amount``` that can lead to unexpected behavior.

## Proof of Concept
There is no means to check inside ```_delegateMulti``` function that either all amount is processed after transfer or still some dust is remaining.

```
            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);
            }
        }
```

## Tools Used
Manual code Review
## Recommended Mitigation Steps
Set the amount variable to zero that ensures no amount remains after the process.
```
            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);
            }
+ 108:            amount = amounts[transferIndex];
        }
```



## Assessed type

Other
Some `ERC20Votes` tokens will not work if you set `approve()` to any value without setting to zero first
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Not setting allowance to Zero before changing the allowance can hinder the deployment of the `ERC20ProxyDelegator` contract for some ERC20 tokens

## Proof of Concept
Some ERC20 tokens (like USDT) with Voting enabled will not work when changing the allowance from an existing non-zero allowance value. For example Tether (USDT)'s approve() function will revert if the current approval is not zero, to protect against front-running changes of approvals.

The following attempt to call the `approve()` function without setting the allowance to zero first.

The `ERC20ProxyDelegator` is among the core implementations of the contract anything that can prevent it's proper functioning will break protocol invariants and brick important functions

```solidity
contract ERC20ProxyDelegator {
    constructor(ERC20Votes _token, address _delegate) {
        _token.approve(msg.sender, type(uint256).max);
        _token.delegate(_delegate);
    }
}
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
It is recommended to set the allowance to zero before increasing the allowance and use safeApprove/safeIncreaseAllowance.


## Assessed type

ERC20
token.transferFrom signaling failure by returning false may allow for funds to be stolen
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
ERC20MultiDelegate is expected to work with any ERC20-compliant tokens as long as they provide the same functionality and interface as ERC20Votes from OpenZeppelin. This makes it possible for ERC20MultiDelegate to work with a token that signals `token.transferFrom` failure by returning `false` instead of reverting, as the ERC-20 standard doesn't require `token.transferFrom` to `throw` in every case of failure.

In a situation a user A has in the past delegated tokens to user B and the method `token.transferFrom` is failing and returning false, e.g the functionality is paused, an attacker can delegate tokens he doesn't have to user B and then later, when `token.transferFrom` is back up, call `delegateMulti([userB], [], amountDelegatedByUserA)` to reimburse the tokens from user B and steal the tokens delegated by user A.

## Proof of Concept

Add the following code to `ENSToken.sol` so it's easy to mock a situation `token.transferFrom` fails

```solidity
bool public canTransferFrom = true;

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        if (canTransferFrom) {
            return ERC20.transferFrom(from, to, amount);
        }
        return false;
    }

    function setCanTransferFrom(bool _canTransferFrom) public {
        canTransferFrom = _canTransferFrom;
    }
```

Add the it block below to `test/delegatemulti.js` inside the `describe block` "ENS Multi Delegate"
```javascript
describe("ENS Multi Delegate", () => {
...
it.only("token.transferFrom signaling failure by returning false allows for funds to be stolen", async () => {
      const [_, attacker] = await ethers.getSigners();

      const delegatorTokenAmount = await token.balanceOf(deployer);
      await token.approve(multiDelegate.address, delegatorTokenAmount);

      const delegates = [bob];
      const amounts = [delegatorTokenAmount];

      await multiDelegate.delegateMulti([], delegates, amounts);

      expect(await token.balanceOf(deployer)).to.eq(0);
      expect(await multiDelegate.balanceOf(deployer, delegates[0])).to.eq(
        amounts[0]
      );

      //Method added for POC
      //IRL token's functionalities might have been paused or something else that caused transferFrom to fail.
      token.setCanTransferFrom(false);

      expect(await token.balanceOf(attacker.address)).to.eq(0);
      //Since transferFrom will fail this won't revert
      await multiDelegate
        .connect(attacker)
        .delegateMulti([], delegates, amounts);
      expect(
        await multiDelegate.balanceOf(attacker.address, delegates[0])
      ).to.eq(amounts[0]);

      token.setCanTransferFrom(true);
      await multiDelegate
        .connect(attacker)
        .delegateMulti(delegates, [], amounts);
      expect
The delegator process occurs even if the source and target have the same address. 
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `ERC20MultiDelegate` contract allows a ENS token holder to either:

- Case#1: Transfer tokens from sources to targets to delegate votes, OR
- Case#2: Get reimbursed from sources and burn their votes OR
- Case#3: Deploy a proxy delegate contract to transfer tokens to, followed by delegating the votes to the intended target.

In the case#1, it isn’t checked to see if the sources and targets are the same. As this amounts to transferring the tokens back to proxy address related to the sources, followed by delegating votes back to the source. Which simply wastes gas.

## Proof of Concept
Please place the following `it` block in the `describe("re-deposit",` block as shown below in the `delegatemulti.js`

```solidity
  describe("re-deposit", () => {
  it.only("Audit:: multiple delegate, sources==targets (1:1)", async () => {
      const delegatorTokenAmount = await token.balanceOf(deployer);
      await token.approve(multiDelegate.address, delegatorTokenAmount);
      const delegates = [deployer, alice];
      const amounts = delegates.map(() =>
        delegatorTokenAmount.div(delegates.length)
      );
      await multiDelegate.delegateMulti([], delegates, amounts);

      const delegatorTokenAmountAfter = await token.balanceOf(deployer);
      expect(delegatorTokenAmountAfter.toString()).to.equal("0");

      //   // delegate must have 1/2 of the votes the delegator delegated
      const votesOfDelegate = await token.getVotes(alice);
      expect(votesOfDelegate.toString()).to.equal(
        delegatorTokenAmount.div(delegates.length).toString()
      );

      for (let delegateTokenId of delegates) {
        let balance = await multiDelegate.balanceOf(deployer, delegateTokenId);
        expect(balance.toString()).to.equal(
          delegatorTokenAmount.div(delegates.length).toString()
        );
      }
    });
});
```

```      ✔ Audit:: multiple delegate, sources==targets (1:1) (133ms)```

## Tools Used
Manual Analysis
Hardhat 
## Recommended Mitigation Steps
It is recommended to skip the delegation process if one or all of sources:target pairs are found to have the same addresss.


## Assessed type

Token-Transfer
Incorrect Addresses will be returned via `retrieveProxyContractAddress()` possibly bricking delegations.
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

The `retrieveProxyContractAddress()` function is used to retrieve the address where it casts a `bytes32` to an `address` type.
```solidity
function retrieveProxyContractAddress(
        ERC20Votes _token,
        address _delegate
    ) private view returns (address) {
        bytes memory bytecode = abi.encodePacked(
            type(ERC20ProxyDelegator).creationCode, 
            abi.encode(_token, _delegate)
        );
        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes1(0xff),
                address(this),
                uint256(0), // salt
                keccak256(bytecode)
            )
        );
       //@audit
        return address(uint160(uint256(hash)));
    }
```

The issue is that in the return statement, the `bytes32` variable `hash` is reduced from `256` bits to `160` bits, without checking that the truncated bytes are `zero`. When the `upper` bits are not `zero`, the casted ` bystes32 hash` is not the valid `address` and it instead returns an incorrect `address`.


**The `retrieveProxyContractAddress()` function is used in the functions below**
- [`deployProxyDelegatorIfNeeded()`](https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L176)
- [`transferBetweenDelegators()`](https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L168C1-L169C74)
- [`_reimburse()`](https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L144C1-L149C6)

An incorrect address would be returned from `retrieveProxyContractAddress()` and then used in the functions above which would brick delegations as incorrect addresses are retrieved.

- **One Instance of how this can affect the protocol**
```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) {
            new ERC20ProxyDelegator{salt: 0}(token, delegate);
            emit ProxyDeployed(delegate, proxyAddress);
        }
        return proxyAddress;
    }
```
Here if a wrong address is retrieved when it is checked and doesn't have a bytecode a new contract will be deployed and when called by `createProxyDelegatorAndTransfer()`
```solidity
    function createProxyDelegatorAndTransfer(
        address target,
        uint256 amount
    ) internal {
        address proxyAddress = deployProxyDelegatorIfNee
Delegation transfer might become temporarily unavailable due to input format in `_delegateMulti`
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
A user might mint a valid delegation token to a `target` delegate which cannot be transferred to another `target` delegate through `_processDelegation` due to `_delegateMulti`'s extension of `address` type of data into the space of `uint256`. 

## Proof of Concept
As `_delegateMulti` takes arrays of `uint256` as inputs:

```solidity
function _delegateMulti(
        uint256[] calldata sources,
        uint256[] calldata targets,
        uint256[] calldata amounts
    ) internal {
...
```
even though the `sources` and `targets` are meant to be `address` as can be seen below in the downcasting from `uint256` into `address`:
```solidity
address source = transferIndex < sourcesLength
                ? address(uint160(sources[transferIndex]))
                : address(0);
            address target = transferIndex < targetsLength
                ? address(uint160(targets[transferIndex]))
                : address(0);
``` 
it is possible to input `2 ** 96` different `uint256` inputs for a certain address by filling up the remaining 96 bits arbitrarily. As the minting/burning of the `ERC1155` delegation tokens are done by passing these `uint256` inputs as token `id`s:
```solidity
if (sourcesLength > 0) {
            _burnBatch(msg.sender, sources, amounts[:sourcesLength]);
        }
        if (targetsLength > 0) {
            _mintBatch(msg.sender, targets, amounts[:targetsLength], "");
        }
```
tokens with different `id`s for the same delegatee can be minted. This in turn can create a temporary availability issue as the balance checker function `getBalanceForDelegate` assumes that the tokens are minted to the `id` of the trivial expansion of `address` into `uint256` by filling the remaining 96 bits by 0s:

```solidity
   function getBalanceForDelegate(
        address delegate
    ) internal view returns (uint256) {
        return ERC1155(this).balanceOf(msg.sender, uint256(uint160(delegate)));
    }
``` 
This function is utilized in `_processDelegation` which is responsible for transferring delegation rights between delegatees:

```solidity
    function _processDelegation(
        address source,
        address target,
        uint256 amount
    ) internal {
        uint256 balance = getBalanceForDelegate(source);

        assert(amount <= balance);
        ...
```  
so it is possible for a user to mint a delegation token to a specific delegatee address, but then be unable to transfer it to another delegatee address as the balance check above would return 0 even though they have tokens that were minted for that address. 

It is possible to resolve this unavailability issue by burning these delegation tokens through `_reimburse` then delegating again hence the issue is temporary thus we think a medium severity is appropriate. Yet, we think that this recovery path defeats the purpose of this contract itsel
No check proxy contract is deployed
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

The ```deployProxyDelegatorIfNeeded``` function in ```ERC20MultiDelegate.sol``` does not verify that a proxy contract has been successfully deployed before emitting an event indicating its creation.

## Impact
Both ```createProxyDelegatorAndTransfer``` and ```_processDelegation``` functions rely on ```deployProxyDelegatorIfNeeded``` to create a proxy address if one does not already exist for a target delegate and subsequently sends tokens to the created proxy. 
If the proxy isn't created successfully tokens meant to be sent to the proxy would not be transferred, causing the transaction to fail. Furthermore, an event will have been emitted which provides false information and users will have wasted gas gas on a transaction that didn't achieve its intended purpose.

## Proof of Concept
Heavy congestion on the network may mean that transactions with lower gas prices are not picked up by miners quickly. This could delay the deployment of the proxy contract or in the case of severe congestion the transaction might never be mined.

Other reasons preventing it from being deployed properly would be chain reorgs and gas limit too low in the transaction to deploy the proxy.

## Tools Used
Manual Review
Hardhat Testing

## Recommended Mitigation Steps
Add a check to verify that the contract has indeed been created before emitting the event and sending tokens to the contract.

```
    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) {
            new ERC20ProxyDelegator{salt: 0}(token, delegate);
            // @audit-issue : check contract created correctly SPARKN
            emit ProxyDeployed(delegate, proxyAddress);
        }

        // if the proxy contract has not been deployed, deploy it
        if (bytecodeSize == 0) {
           new ERC20ProxyDelegator{salt: 0}(token, delegate);
        
 +         // Verify if the contract was deployed successfully
 +         uint newBytecodeSize;
 +         assembly {
 +              newBytecodeSize := extcodesize(proxyAddress)
 +         }
 +         require(newBytecodeSize > 0, "Proxy deployment failed");
        }
        return proxyAddress;
    }
```


## Assessed type

Error
Possible hash collision in retrieveProxyContractAddress()
Audit: 2023-10-ens-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L202
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L206
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


# Vulnerability details

## Impact
implemention of `keccak256(abi.encodePacked(a, b))` with both dynamic types or same type with dynamic nature leads to collision in hash.

## Proof of Concept
From the sol docs:[link](https://docs.soliditylang.org/en/v0.8.17/abi-spec.html?highlight=collisions#non-standard-packed-mode:~:text=no%20decoding%20function.-,Warning,-If%20you%20use). i.e If you use `keccak256(abi.encodePacked(a, b))` and both a and b are dynamic types,
 it is easy to craft collisions in the hash value by moving parts of a into b and vice-versa. More specifically, `abi.encodePacked("a", "bc") == abi.encodePacked("ab", "c")`. and  make sure to always use the same types and check that at most one of them is dynamic.
```
function retrieveProxyContractAddress(
        ERC20Votes _token,
        address _delegate
    ) private view returns (address) {
        bytes memory bytecode = abi.encodePacked(
            type(ERC20ProxyDelegator).creationCode, 
            abi.encode(_token, _delegate)
        );
        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes1(0xff),
                address(this),
                uint256(0), // salt
                keccak256(bytecode)
            )
        );
        return address(uint160(uint256(hash)));
    }
```
the `retrieveProxyContractAddress()` function implements the `keccak256(abi.encodePacked(a, b))` and here `type(ERC20ProxyDelegator).creationCode` returns bytes memory and It is a dynamic type because its length can vary depending on the contract's constructor and its parameters and the `abi.encode(_token, _delegate)` returns bytes memory.which can lead to collision in hash, and functions using `retrieveProxyContractAddress` will all be affected


## Tools Used
Manual Review
## Recommended Mitigation Steps
use abi.encode instead of abi.encodePacked


## Assessed type

en/de-code