xanoutas/solidity-security-findings huggingface dataset:
Not enough valid checks in the Factory.sol
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/util/Factory.sol#L36-L53


# Vulnerability details

## Impact
The LiquidityPoolFactory contract is used to deploy LiquidityPools through a controlled newLiquidityPool() , however a vital check is omitted.
The newLiquidityPool() is first called in the PoolManager.sol but it is not restricted to just that, it can also be deployed by any ward in the LiquidityPoolFactory.sol In the PoolManager.sol, a check is done to verify that the tranche token exists and that the currency is allowed for the pool but none of those checks are done in the LiquidityPoolFactory.sol

## Proof of Concept
As we can see the checks were correctly made in the function below:
```solidity
 function deployLiquidityPool(uint64 poolId, bytes16 trancheId, address currency) public returns (address) {
       Tranche storage tranche = pools[poolId].tranches[trancheId];
       require(tranche.token != address(0), "PoolManager/tranche-does-not-exist"); // Tranche must have been added
       require(isAllowedAsPoolCurrency(poolId, currency), "PoolManager/currency-not-supported"); // Currency must be supported by pool

       address liquidityPool = tranche.liquidityPools[currency];
       require(liquidityPool == address(0), "PoolManager/liquidityPool-already-deployed");
       require(pools[poolId].createdAt != 0, "PoolManager/pool-does-not-exist");

        address[] memory liquidityPoolWards = new address[](1);
        liquidityPoolWards[0] = address(investmentManager);
        liquidityPool = liquidityPoolFactory.newLiquidityPool(
            poolId, trancheId, currency, tranche.token, address(investmentManager), liquidityPoolWards
        );
```

```solidity
function newLiquidityPool(
       uint64 poolId,
       bytes16 trancheId,
       address currency,
       address trancheToken,
       address investmentManager,
       address[] calldata wards
   ) public auth returns (address) {
       LiquidityPool liquidityPool = new LiquidityPool(poolId, trancheId, currency, trancheToken, investmentManager);

       liquidityPool.rely(root);
       for (uint256 i = 0; i < wards.length; i++) {
           liquidityPool.rely(wards[i]);
       }
       liquidityPool.deny(address(this));
       return address(liquidityPool);
   }
```
No checks exist for the pool contract being deployed, whereas deploying a pool with incompatible parameters or an already deployed contract would not revert when deployed via the factory.
## Tools Used

Manual Review
## Recommended Mitigation Steps

Ensure all validations with the factory are checked by calling:
isAllowedAsPoolCurrency(uint64 poolId, address currencyAddress) and getLiquidityPool(uint64 poolId, bytes16 trancheId, address currency) to check if it is already deployed and if the currency is allowed


## Assessed type

Other
Assumptions are currently made that prices would forever be positive
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L223-L232


# Vulnerability details



## Impact

Neglecting the potential for negative asset prices can lead to inaccurate value representation in the Liquidity Pool, possibly affecting calculations related to assets and tokens. It's crucial to note that the value of an asset, even if negative in the real world, may not mean it's entirely lost; its value might bounce back to a positive number.

## Proof of Concept

`LiquidityPool` seems to operate under the assumption that the prices of assets will always remain positive. This could become a point of contention, especially when considering `real-world assets` which have the potential to trade at negative values, as history has shown.

1. **Testing Assumptions**: In various tests, like those in `Messages.t.sol`, it's evident that prices are always assumed positive.
2. **Contract Limitation**: The manner in which prices are managed in the contract is a concern. Prices are inherently taken to be `uint` (unsigned integers). However, considering that we're dealing with real-world assets, the design choice seems flawed. Real-world assets have, in the past, traded for negative values. For instance:

[InvestmentManager.sol#L223-L232](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L223-L232)

```solidity
function updateTrancheTokenPrice(uint64 poolId, bytes16 trancheId, uint128 currencyId, uint128 price)
    public
    onlyGateway
{
    ...//ommited for brevity reasons
    LiquidityPoolLike(liquidityPool).updatePrice(price);
}
```

Here, the price is assumed to always be a positive `uint128`.

3. **Price Dynamics Example**: If a pool is composed of 80% oil and 20% USD, and the value of oil goes negative, the current system would fail to handle this scenario accurately. This system's design, which imagines an always positive APR growth, does not take into account negative pricing events:

```
By changing prices, provided by the Centrefuge bot when users redeem:
Pool with 5% APR -> Current price is 1:1 DAI to share.
Invest 100 DAI for 100 shares -> After 1 year, price is 1.05 DAI per share.
Redeem 100 shares for 105 DAI.
```

4. **Contrast with Established Protocols**: Notably, established oracles such as Chainlink return prices in `int256` format precisely because `real-world assets` can have negative prices. Limiting price representation to `uint256` is not only misaligned with industry practices but also poses risks.

It's worth noting that adding currencies or launching pools requires governance votes by CFG holders on Centrifuge. While this provides a check on which currencies can be added or pools launched, it's still pivotal to address the system's inherent limitations. Also, if an asset defaults, the recovery process, such as buying out and replacing a non-performing loan, would face comple
Already allowed pool currency can not be removed
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Already allowed pool currency can not be removed. Once a currency is allowed, it can not be removed. Most of the time, the currency will be a stablecoin but in cases where the stablecoin depegs, as seen with USDC three months ago, new liquidity pools for the underlying Centrifuge pool can be created where the actual price will be different.

## Tools Used
Mannaul review 

## Recommended Mitigation Steps
Add the possibility to remove an already allowed currency.


## Assessed type

Other
Contract lacks proper error handling. Without meaningful error messages, it becomes difficult to identify the exact reasons for failures or unexpected behavior. Often leading end user astray.
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

### Contract lacks proper error handling. Without meaningful error messages, it becomes difficult to identify the exact reasons for failures or unexpected behavior. Often leading end user astray.
The code lacks proper error handling mechanisms, which can make it challenging to diagnose issues or vulnerabilities. Without meaningful error messages, it becomes difficult to identify the exact reasons for failures or unexpected behavior.

### Code Block Examples:

**Current Implementation:**
```solidity
function file(bytes32 what, uint256 data) external auth {
    if (what == "delay") {
        require(data <= MAX_DELAY, "Root/delay-too-long");
        delay = data;
    } else {
        revert("Root/file-unrecognized-param");
    }
    emit File(what, data);
}
```

**Recommended Mitigation:**

Implement Error Handling with Informative Messages:
```solidity
function file(bytes32 what, uint256 data) external auth {
    if (what == "delay") {
        require(data <= MAX_DELAY, "Root: Delay value exceeds the maximum allowed");
        delay = data;
    } else {
        revert("Root: Unrecognized parameter");
    }
    emit File(what, data);
}
```

**Explanation:**

In the updated code:

1. We've provided informative error messages that describe the reason for the failure. This makes it easier to understand and diagnose issues.

2. When the condition for changing the `delay` value is not met, the error message clearly indicates that the delay value exceeds the maximum allowed.

3. When an unrecognized parameter is provided, the error message indicates that it's an unrecognized parameter.



## Assessed type

Error
The price update timestamp  doesn't get checked, allowing for the use of stale prices
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/LiquidityPool.sol#L72
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/LiquidityPool.sol#L75
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/LiquidityPool.sol#L324-L328


# Vulnerability details

## Impact

It allows for the use of stale prices in cases where they will either damage the protocol or the user.  

## Proof of Concept

The protocol has a price oracle system where the price gets updated by an account with a privileged role by calling `updatePrice()`.

```jsx
function updatePrice(uint128 price) public auth {
	latestPrice = price;
	lastPriceUpdate = block.timestamp;
	emit UpdatePrice(price);
}
```

The issue arises due to `lastPriceUpdate` not getting used in order to make sure old prices do not get used when interacting with the protocol.

## Tools Used

Manual review

## Recommended Mitigation Steps

Consider implementing the following constant value:

```jsx
uint256 public constant MAX_STALENESS_IN_SECONDS = 5 * 60 * 60; // 5 hours
```

Also add the following check before any place where `latestPrice` is used. 

```jsx

require (block.timestamp - MAX_STALENESS_IN_SECONDS <= lastPriceUpdate, "stale price");
```


## Assessed type

Other
Unchecked Minting
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/LiquidityPool.sol#L295
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/token/Tranche.sol#L72


# Vulnerability details

## Impact
There is a possibility, an attacker can force minting of tokens to self or any other diluting the token value and stealing funds in the process

## Proof of Concept
Here is a step-by-step process
- The attacker calls the `transferFrom` function with msg.data calling the mint function in the share contract
- The restriction check works as it calls the address of the liquidity pool which is a valid isForwarder
- This allows the attacker to mint to his address and redeem later

## Tools Used
Manual review
## Recommended Mitigation Steps
Add the auth modifier for the `transfer` and `transferFrom` function also


## Assessed type

Token-Transfer
SafeTransferLib's safeApprove() does not set allowance 0 first which would lead to the escrow encountering issues when dealing with tether's USDT or tokens like it.
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/0af232255c7d045efde4ac40801dfeeed8a8d889/src/Escrow.sol#L22-L26


# Vulnerability details


## Impact

Medium... a number of features within the protocol will not work if an approval reverts in the escrow or anywhere else

NB: Report mainly focuses on the usage of the SafeTransferLib's safeApprove(), but bug is attached to the underlying call made to `approve()` which can also be seen in the `PoolManager.sol` contract

<details>

<summary>Click to see PoolManager's affected code reference</summary>

```solidity
    function handleTransfer(uint128 currency, address recipient, uint128 amount) public onlyGateway {
        address currencyAddress = currencyIdToAddress[currency];
        require(currencyAddress != address(0), "PoolManager/unknown-currency");
    //@audit instance of not  approving to zero first
        EscrowLike(escrow).approve(currencyAddress, address(this), amount);
        SafeTransferLib.safeTransferFrom(currencyAddress, address(escrow), recipient, amount);
    }

```

</details>

## Proof of Concept

Pools are going to accept investments with stable coins making the chances of this happening higher, note that USDT is the most adopted stablecoin out there and Tether (USDT)'s `approve()` function will revert if the current approval is not zero, to protect against front-running changes of approvals.

Now take a look at [Escrow.sol#L22-L26](https://github.com/code-423n4/2023-09-centrifuge/blob/0af232255c7d045efde4ac40801dfeeed8a8d889/src/Escrow.sol#L22-L26)

```solidity
    // --- Token approvals ---
    function approve(address token, address spender, uint256 value) external auth {
        SafeTransferLib.safeApprove(token, spender, value);
        emit Approve(token, spender, value);
    }
```

`SafeTransferLib.safeApprove()` just encodes the call with the normal ERC20 `approve()` selector, as can be seen [here](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/util/SafeTransferLib.sol#L36-L39)

```solidity
    function safeApprove(address token, address to, uint256 value) internal {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "SafeTransferLib/safe-approve-failed");
    }
```

Which means that whenever USDT or tokens that have the same implementation regarding it's approval is used, then the approval is not going to work does not work when changing the allowance from an existing `non-zero` allowance value.

## Tool 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

Context
previewWithdraw doesn't round up the asset amount leading to confusion when external protocols integrate with the vaults of the protocol
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/InvestmentManager.sol#L338-L347
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/InvestmentManager.sol#L569-L582


# Vulnerability details

## Impact

Protocols, that integrate with Centrifuge liquidity pools may wrongly assume that withdraw rounds the amount up as per the ERC-4626 specification leading to a wide array or issues for both parties.  

## Proof of Concept

The following is stated in the EIP's [security considerations](https://eips.ethereum.org/EIPS/eip-4626#security-considerations) suggests that any withdraw function should round-up instead of down so that the protocol is fully EIP-4626 compliant:

> Finally, EIP-4626 Vault implementers should be aware of the need for specific, opposing rounding directions across the different mutable and view methods, as it is considered most secure to favour the Vault itself during calculations over its users:
> 
> - If (1) it’s calculating how many shares to issue to a user for a certain amount of the underlying tokens they provide or (2) it’s determining the amount of the underlying tokens to transfer to them for returning a certain amount of shares, it should round *down*.
> - If (1) it’s calculating the amount of shares a user has to supply to receive a given amount of the underlying tokens or (2) it’s calculating the amount of underlying tokens a user has to provide to receive a certain amount of shares, it should round *up*.

The case with the protocol's implementation of the standard is different.

Both in `convertToAssets()` and `_calculatePrice()` the amounts get rounded down instead of up:

```jsx
assets = shares.mulDiv(
	LiquidityPoolLike(liquidityPool).latestPrice(),
	10 ** (PRICE_DECIMALS + trancheTokenDecimals - currencyDecimals),
	// @audit here:
	MathLib.Rounding.Down
);
```

```jsx
depositPrice = currencyAmountInPriceDecimals.mulDiv(
	10 ** PRICE_DECIMALS, trancheTokenAmountInPriceDecimals,
  // @audit here:
	MathLib.Rounding.Down
);
```

## Tools Used

Manual review

## Recommended Mitigation Steps

Consider changing `convertToAssets()`'s expression to the following:

```jsx
assets = shares.mulDiv(
	LiquidityPoolLike(liquidityPool).latestPrice(),
	10 ** (PRICE_DECIMALS + trancheTokenDecimals - currencyDecimals),
	MathLib.Rounding.Up
);
```

And also consider changing `_calculateTrancheTokenAmount()` to the following so that it rounds up in the case of it getting called by `processWithdraw()` or `previewWithdraw()`, and so that it rounds down in the case of getting called by `processDeposit()` or `previewDeposit()`.

```jsx
function _calculateTrancheTokenAmount(uint128 currencyAmount, address liquidityPool, uint256 price, bool roundUp)
	internal
	view
	returns (uint128 trancheTokenAmount)
{

	// ...

	depositPrice = currencyAmountInPriceDecimals.mulDiv(
		10 ** PRICE_DECIMALS, trancheTokenAmountInPriceDecimals,
		roundUp ? MathLib.Rounding.Down : MathLib.Rounding.Up
	);

	// ...
}
```


#
addPauser should be a two-step process in PauseAdmin.sol
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/admins/PauseAdmin.sol#L34-L37


# Vulnerability details

## Impact
Adding new pauser should me two-step proccess. The identified issue pertains to the absence of a two-step process for adding a new pauser in the smart contract. This vulnerability could potentially allow an unauthorized entity to gain control over pausing and unpausing critical functions within the contract. Such control could lead to disruptions, unintended consequences, or exploitation.

Reference: See finding #6 from Trail of Bits audit of Hermez Network: https://github.com/trailofbits/publications/blob/master/reviews/hermez.pdf
## Proof of Concept
If by miskate someone is added as pauser , he can pause protocol which will brinks entire functionality

## Tools Used
Manual Review
## Recommended Mitigation Steps
Adding two step proccess for important changes as adding pauser role. 


## Assessed type

Access Control
possibility of reentrancy attack when `poolManger.sol#Transfer` called with malicious recipient contract address
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

when users calls the `transfer` function in the `poolManger.sol` the transaction data will be send to the centrifuge chain first and then it will be back to the router and then direct it to the`handleTransfer` function in `poolManger.sol`, user can make a reentrancy attack and drain the escrow contract. more details in the POC.

## Proof of Concept

the function `transfer` is as follow:

```solidity
  function transfer(
        //@audit possibility of reentrancy attack when the call comes back to the recipient
        address currencyAddress,
        bytes32 recipient,
        uint128 amount
    ) public {
        uint128 currency = currencyAddressToId[currencyAddress];
        require(currency != 0, "PoolManager/unknown-currency");
        //we give the amount to escrow till the transaction returns back from the centrifuge chain
        SafeTransferLib.safeTransferFrom(
            currencyAddress,
            msg.sender,
            address(escrow),
            amount
        );
        gateway.transfer(currency, msg.sender, recipient, amount);
    }
```

the call to this function will lead to call the gateway `transfer` function to encode data and send it to the centrifuge chain, however if we imagine that the transfer will happen using a currency that have transfer callback function on specific chain and if we put the `recipient` address to a contract which calls the currency callback function with logic to make a re call to the escrow to send all the tokens to our contract then we can drain the escrow contract, tokens with this functionality can be exist as we have xDai chain too which the USDC, WBTC, and WETH contained post-transfer callback procedures. even if the centrifuge did not support the xDai chain there is many other chains that the stable coin contracts are upgradable and can contain `post-transfer callback` function some day. to read more about the xDai problem check this [link](https://forum.gnosis.io/t/gip-31-should-gnosis-chain-perform-a-hardfork-to-upgrade-the-token-contract-vulnerable-to-the-reentrancy-attack/4134)

however, when the call comes the `handleTransfer` will be called which the reentrancy attck can happen by our contract(recipient address)

```solidity
 function handleTransfer(
        uint128 currency,
        address recipient,
        uint128 amount
    ) public onlyGateway {
        address currencyAddress = currencyIdToAddress[currency];
        require(currencyAddress != address(0), "PoolManager/unknown-currency");

        EscrowLike(escrow).approve(currencyAddress, address(this), amount);
        SafeTransferLib.safeTransferFrom(
            currencyAddress,
            address(escrow),
            recipie
potential overflow in max.Deposit&maxMint and max.redeem&max.withdraw
Audit: 2023-09-centrifuge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L619
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L467
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L451
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L427


# Vulnerability details

## Impact
there is potential overflow in max.deposit and max.mint 

## Proof of Concept
there is only way to decrease max.deposit and max.mint is _deposit to be excuted .


              function _deposit(uint128 trancheTokenAmount, uint128 currencyAmount, address liquidityPool,  
     address user)
        internal
       {
        LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPool);

        _decreaseDepositLimits(user, liquidityPool, currencyAmount, trancheTokenAmount); // decrease the possible deposit limits
        require(lPool.checkTransferRestriction(msg.sender, user, 0), "InvestmentManager/trancheTokens-not-a-member");
        require(
            lPool.transferFrom(address(escrow), user, trancheTokenAmount),
            "InvestmentManager/trancheTokens-transfer-failed"
        );

        emit DepositProcessed(liquidityPool, user, currencyAmount);
    }


If we don't call processMint or processDeposit , there is no decreasing amount in max.deposit and maxmint .if investor don't call deposit or mint in liquidityPool contract and keep investing .let's assume max.deposit&&max.mint is nearly 2^256 cause investors keep investing and didn't call deposit or mint in liquiditypool contract, when investors call requestDeposit again with some amount and that transaction will be reverted cause of overflow in max.deposit and max.mint .investor can't call request
this scenario can't also happen in max.redeem and max.withdraw.

## Tools Used
manual view 
## Recommended Mitigation Steps
call function processDeposit or processMint first at Deposit or mint function at liquidity pool contract.


## Assessed type

DoS