xanoutas/solidity-security-findings huggingface dataset:
UserEscrow is not compatible with tokens that do not function with max approvals such as UNI or COMP
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/UserEscrow.sol#L43


# Vulnerability details

## vulnerability Details
in user escrow the `transferOut` function sets approval to the receiver to uint256.max
```solidity
 function transferOut(address token, address destination, address receiver, uint256 amount) external auth {
        require(destinations[token][destination] >= amount, "UserEscrow/transfer-failed");
        require(
            /// @dev transferOut can only be initiated by the destination address or an authorized admin.
            ///      The check is just an additional protection to secure destination funds in case of compromized auth.
            ///      Since userEscrow is not able to decrease the allowance for the receiver,
            ///      a transfer is only possible in case receiver has received the full allowance from destination address.
            receiver == destination || (ERC20Like(token).allowance(destination, receiver) == type(uint256).max),
            "UserEscrow/receiver-has-no-allowance"
        );
        destinations[token][destination] -= amount;

        SafeTransferLib.safeTransfer(token, receiver, amount);
        emit TransferOut(token, receiver, amount);
    }
}
```
This is a problem for tokens that are not compatible for max approvals.
> Some tokens (e.g. UNI, COMP) revert if the value passed to approve or transfer is larger than uint96.
Both of the above tokens have special case logic in approve that sets allowance to type(uint96).max if the approval amount is uint256(-1), which may cause issues with systems that expect the value passed to approve to be reflected in the allowances mapping.

additionally the protocol has stated in the readme that only rebasing or airdrop tokens are not allowed every other token is, so this finding is valid.

## Impact
User's who will try to withdraw tokens such as UNI or COMP will have their tokens stuck indefinitely

## Proof of Concept
1. user deposits UNI or COMP
2. user changes mind and wants to pull tokens out.
3. Users funds are in UserEscrow.
4. because of the logic of `TransferOut`, users will not be able to withdraw tokens because this function approves the receiver to uin256.max.
5. Users funds are stuck

## Tools Used
manual review
## Recommended Mitigation Steps
allow approval to be lower instead of the max approval, that way the protocol is compatible with UNI and COMP and other tokens that do not allow max approvals.


## Assessed type

ERC20
Several storage variables in the contract are declared as public, which means their state can be read by anyone. This exposure of sensitive contract data can pose security and privacy risks.
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/PoolManager.sol#L91


# Vulnerability details

### Several storage variables in the contract are declared as public, which means their state can be read by anyone. This exposure of sensitive contract data can pose security and privacy risks.


**Impact:** Several storage variables in the contract are declared as public, which means their state can be read by anyone. This exposure of sensitive contract data can pose security and privacy risks.

**Proof of Concept:** In the provided contract code, there are several state variables declared as public, which allows anyone to read their values. Here are some examples:

```solidity
mapping(uint64 => Pool) public pools;
mapping(uint128 => address) public currencyIdToAddress;
mapping(address => uint128) public currencyAddressToId;
```

These public variables include `pools`, `currencyIdToAddress`, and `currencyAddressToId`. This means that the state of these variables can be accessed by external entities, potentially exposing sensitive information about the contract's state.

**Recommended Mitigation Steps:** To enhance security and privacy, consider restricting the access modifier of these variables to `internal` or `private` if their state should not be publicly accessible. Here are the recommended steps:

1. Change Access Modifiers: Modify the access modifiers of these variables to `internal` or `private` as appropriate based on their use cases. For example:

   ```solidity
   mapping(uint64 => Pool) internal pools;
   mapping(uint128 => address) private currencyIdToAddress;
   mapping(address => uint128) private currencyAddressToId;
   ```

   By doing this, you limit external access to these variables and make them accessible only within the contract or derived contracts (for `internal`) or within the current contract (for `private`).

2. Implement Getter Functions: If there's a need for external access to certain variable values, implement getter functions that control access and provide only the necessary information. For example:

   ```solidity
   function getPool(uint64 poolId) external view returns (Pool memory) {
       return pools[poolId];
   }
   ```

   This way, you can selectively expose data to external callers in a controlled manner while keeping the actual state variables private or internal.

By making these changes, you can improve the security and privacy of the contract's state variables, ensuring that sensitive data is not exposed to the public by default.


## Assessed type

Other
Gateway uses only one outgoing router
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/gateway/Gateway.sol#L78-L90


# Vulnerability details

## Impact
The Gateway contract is the entrance point for both the XCM router and the Axelar Router. 
In the gateway contract, provisions are made for multiple incoming routers via the gateway. But only one address is permitted permitted for the outgoing router. 
I believe this is an oversight from the team as both routers are tailored to work simultaneously with the gateway

## Proof of Concept
```solidity

/// @title  Gateway
/// @dev    It parses incoming messages and forwards these to Managers
///         and it encoded outgoing messages and sends these to Routers.
///
///         If the Root is paused, any messages in and out of this contract
///         will not be forwarded

    mapping(address => bool) public incomingRouters;
    RouterLike public outgoingRouter;
```

This limits the functionality of the protocol if each gateway has to be swapped before calls are completed
## Tools Used
Manual Review
## Recommended Mitigation Steps
Allow for the 2 gateways to be used simultaneously in the contract.


## Assessed type

Other
type(uint256).max used instead of type(uint128).max
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/UserEscrow.sol#L43


# Vulnerability details

## Impact
`type(uint256).max` used instead of `type(uint128).max` in UserEscrow.transferOut function.

## Proof of Concept
InvestmentManager._redeem calls via processRedeem, which calls to `transferOut` function in UserEscrow contract to currencyAmount from the user to the receiver, in transferOut function, destination address is giving full allowance to receiver address as,

#L43
```
 require(
            /// @dev transferOut can only be initiated by the destination address or an authorized admin.
            ///      The check is just an additional protection to secure destination funds in case of compromized auth.
            ///      Since userEscrow is not able to decrease the allowance for the receiver,
            ///      a transfer is only possible in case receiver has received the full allowance from destination address.
            receiver == destination || (ERC20Like(token).allowance(destination, receiver) == type(uint256).max),
            "UserEscrow/receiver-has-no-allowance"
        );
```
the issue here is it gives wrong allowance to receiver address, which should be `type(uint128).max` as the `currencyAmount` used in `InvestmentManager.redeem` function is `uint128` which itself gives a total understanding that the full allowance should not exceed the maximum value of `uint128`.

InvestmentManager.sol #L535-L548
```
   function _redeem(
        uint128 trancheTokenAmount,
        uint128 currencyAmount,
        address liquidityPool,
        address receiver,
        address user
    ) internal {
        LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPool);

        _decreaseRedemptionLimits(user, liquidityPool, currencyAmount, trancheTokenAmount); // decrease the possible deposit limits
        userEscrow.transferOut(lPool.asset(), user, receiver, currencyAmount);

        emit RedemptionProcessed(liquidityPool, user, trancheTokenAmount);
    }
```
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/InvestmentManager.sol#L535-L548

## Tools Used
Manual

## Recommended Mitigation Steps
Change the allowance to `type(uint128).max`, `(ERC20Like(token).allowance(destination, receiver) == type(uint128).max)`


## Assessed type

Other
ERC20 functions in LiquidityPool are not overridden
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/LiquidityPool.sol#L270-L322
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/token/ERC20.sol#L91-L119


# Vulnerability details

## Proof of Concept

In LiquidityPool contract, it uses some ERC20 functionality such as transfer and transferfrom. The override keyword is not on those functions, they may not work as intended. 

```
    // --- ERC20 overrides ---
    function transferFrom(address, address, uint256) public returns (bool) {
        (bool success, bytes memory data) = address(share).call(bytes.concat(msg.data, bytes20(msg.sender)));
        _successCheck(success);
        return abi.decode(data, (bool));
    }


    function transfer(address, uint256) public returns (bool) {
        (bool success, bytes memory data) = address(share).call(bytes.concat(msg.data, bytes20(msg.sender)));
        _successCheck(success);
        return abi.decode(data, (bool));
    }
```

In Centrifuge ERC20.sol, the functions transfer and transferFrom has a virtual modifier. They should be overridden in the Liquidity Pool contract

```
    function transfer(address to, uint256 value) public virtual returns (bool) {
        require(to != address(0) && to != address(this), "ERC20/invalid-address");
        uint256 balance = balanceOf[_msgSender()];
        require(balance >= value, "ERC20/insufficient-balance");


        unchecked {
            balanceOf[_msgSender()] = balance - value;
            balanceOf[to] += value;
        }


        emit Transfer(_msgSender(), to, value);


        return true;
    }


    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        require(to != address(0) && to != address(this), "ERC20/invalid-address");
        uint256 balance = balanceOf[from];
        require(balance >= value, "ERC20/insufficient-balance");


        if (from != _msgSender()) {
            uint256 allowed = allowance[from][_msgSender()];
            if (allowed != type(uint256).max) {
                require(allowed >= value, "ERC20/insufficient-allowance");
                unchecked {
                    allowance[from][_msgSender()] = allowed - value;
                }
            }
        }
```

## Impact

The wrong function may be used

## Tools Used

Manual Review

## Recommended Mitigation Steps

Override the appropriate functions in the liquidity pool contract


## Assessed type

ERC20
the `newTrancheToken` will always revert due to incorrect implementation of `file()` function
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/token/Tranche.sol#L42-L46
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/util/Factory.sol#L98-L99


# Vulnerability details

## impact

The `newTrancheToken` function is intended for deploying new tranche tokens for a pool. However, this function calls the` trancheToken.sol#file()` function to set the name, symbol, and restriction manager for the tranche token. The problem arises because the `file()` function is designed to revert transactions if the string passed to it is not

## proof of concepts

The `Factory#newTrancheToken` function invokes the file function to set the name and symbol of the tranche token by providing them as strings:

```solditi
 function newTrancheToken(
        uint64 poolId,
        bytes16 trancheId,
        string memory name,
        string memory symbol,
        uint8 decimals,
        address[] calldata trancheTokenWards,
        address[] calldata restrictionManagerWards
    ) public auth returns (address) {
        address restrictionManager = _newRestrictionManager(
            restrictionManagerWards
        );

        // Salt is hash(poolId + trancheId)
        // same tranche token address on every evm chain
        bytes32 salt = keccak256(abi.encodePacked(poolId, trancheId));

        TrancheToken token = new TrancheToken{salt: salt}(decimals);
        //@audit here we revert as the what is not restrictionManager !
        token.file("name", name);
        token.file("symbol", symbol);
        token.file("restrictionManager", restrictionManager);
        //audit there is no function called relay in tranche token contract
        token.rely(root);
        for (uint256 i = 0; i < trancheTokenWards.length; i++) {
            token.rely(trancheTokenWards[i]);
        }
        token.deny(address(this));

        return address(token);
    }
```

the file function implemented as follow:

```solidity
 function file(bytes32 what, address data) public auth {
        if (what == "restrictionManager") restrictionManager = ERC1404Like(data);
        else revert("TrancheToken/file-unrecognized-param");
        emit File(what, data);
    }
```

If the `what` parameter is not `"restrictionManager"` the transaction will revert. Since we pass the name and symbol as the what parameter, the call to the `newTrancheToken` function will always result in reverting.

You can observe this behavior in the contract I created in Remix, as the function will revert when calling call with "name" and "symbol" strings.

```
pragma solidity ^0.8.21;

contract test{
  address private owner;
  event File(bytes32 , address);
constructor() public {

    owner = msg.sender;
}

 function file(bytes32 what, address data) public returns(bool) {
        if (what == "restrictionManager") {
            return true;
        }
        else revert("ok we revert");
        emit File(what,
Approve function of ERC20.sol is prone to race condition vulnerability
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/token/ERC20.sol#L131-L159


# Vulnerability details

## Impact
The race condition that happens most frequently on the network today is the race condition in the ERC20 token standard. 
The ERC20.sol includes a function called 'approve', which allows an address to approve another address to spend tokens on their behalf.

 Assume that Alice has approved Eve to spend n of her tokens, then Alice decides to change Eve's approval to m tokens.
 Alice submits a function call to approve with the value n for Eve. Eve runs an Ethereum node, so she knows that Alice is going to change her approval to m.
 Eve then submits a transferFrom request, sending n of Alice's tokens to herself, but gives it a much higher gas price than Alice's transaction.
 The transferFrom executes first so gives Eve n tokens and sets Eve's approval to zero. Then Alice's transaction executes and sets Eve's approval to m.
 Eve then sends those m tokens to herself as well. Thus, Eve gets n + m tokens, even though she should have gotten at most max(n,m)

## Proof of Concept
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/token/ERC20.sol#L131-L159

## Tools Used
VSCode

## Recommended Mitigation Steps
The best fix for the ERC20 race condition is to add a field to the inputs of approve, which is the expected current value, 
and to have approve revert if Eve's current allowance is not what Alice indicated she was expecting.
Also, we can add a safe approve function. 

From the user's perspective, it is possible to mitigate the ERC20 race condition by setting approvals to zero before changing them.


## Assessed type

Token-Transfer
Lack of proper input validation in In the `addPool` function and `poolId`
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/PoolManager.sol#L168


# Vulnerability details

### Lack of Input Validation

**Impact:** Lack of input validation can lead to unexpected behavior or vulnerabilities. Input validation is crucial to ensure that the inputs provided to functions are within acceptable ranges and meet the required conditions. Failing to validate inputs can result in unintended consequences or even malicious actions.

**Proof of Concept:** In the provided contract code, several functions lack proper input validation, which leaves room for potential issues:

1. **addPool Function:**
   ```solidity
   function addPool(uint64 poolId) public onlyGateway {
       Pool storage pool = pools[poolId];
       require(pool.createdAt == 0, "PoolManager/pool-already-added");
       pool.poolId = poolId;
       pool.createdAt = block.timestamp;
       emit PoolAdded(poolId);
   }
   ```
   In the `addPool` function, there is no validation of the `poolId` parameter. It is assumed that any `poolId` can be added without checking if it's valid or authorized.

2. **allowPoolCurrency Function:**
   ```solidity
   function allowPoolCurrency(uint64 poolId, uint128 currency) public onlyGateway {
       Pool storage pool = pools[poolId];
       require(pool.createdAt != 0, "PoolManager/invalid-pool");

       address currencyAddress = currencyIdToAddress[currency];
       require(currencyAddress != address(0), "PoolManager/unknown-currency");

       pools[poolId].allowedCurrencies[currencyAddress] = true;
       emit PoolCurrencyAllowed(currency, poolId);
   }
   ```
   In the `allowPoolCurrency` function, while it checks if the `currency` exists in the mapping, it doesn't validate if the `poolId` is valid or if the caller has the necessary permissions.

3. **addTranche Function:**
   ```solidity
   function addTranche(
       uint64 poolId,
       bytes16 trancheId,
       string memory tokenName,
       string memory tokenSymbol,
       uint8 decimals
   ) public onlyGateway {
       Pool storage pool = pools[poolId];
       require(pool.createdAt != 0, "PoolManager/invalid-pool");
       Tranche storage tranche = pool.tranches[trancheId];
       require(tranche.createdAt == 0, "PoolManager/tranche-already-exists");

       tranche.poolId = poolId;
       tranche.trancheId = trancheId;
       tranche.decimals = decimals;
       tranche.tokenName = tokenName;
       tranche.tokenSymbol = tokenSymbol;
       tranche.createdAt = block.timestamp;

       emit TrancheAdded(poolId, trancheId);
   }
   ```
   In the `addTranche` function, while it checks if `trancheId` already exists, it does not validate if `poolId` is a valid pool or if the caller has the necessary permissions.

**Recommended Mitigation Steps:** To address the lack of input validation, you should perform the following steps in the respective functions:

1. **addPool Function:**
   - Validate the `poolId` to ensure
Invalid ``_DOMAIN_SEPARATOR``
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/token/ERC20.sol#L48


# Vulnerability details

## Impact
The domain separator is computed at deployment time. However at the point it is computed the values needed have not been set.
```solidity
//In the constructor
        _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid);
..
..
 function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {
        return keccak256(
            abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                keccak256(bytes(name)),
                keccak256(bytes(version)),
                chainId,
                address(this)
            )
        );
    }
```
## Proof of Concept
As seen above the function used to set the domain separator uses the name, version, and chainID, However the value of the name is not set in the constructor. They are set after deployment. 
Hence this causes the ``DOMAIN_SEPARATOR()`` function to return a different value from what should have been originally computed if the name was available. 

The function ``DOMAIN_SEPARATOR()`` returns the wrong  ``_DOMAIN_SEPARATOR`` every time it is called with the same chainID.
```solidity
    function DOMAIN_SEPARATOR() external view returns (bytes32) {
        return block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid);
    }
```
## Tools Used
Manual Review
## Recommended Mitigation Steps
Do not compute the Domain Separator in the constructor, compute it after the name has been set


## Assessed type

ERC20