xanoutas/solidity-security-findings huggingface dataset:
Tranch tokens can be burnt and unrecoverable if user sends invalid destinationAddress in `transferTrancheTokensToEVM`
Audit: 2023-09-centrifuge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/gateway/Gateway.sol#L316-L317
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/PoolManager.sol#L149-L163
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/PoolManager.sol#L272-L275


# Vulnerability details

## Summary
There is no validation (or apparent refundability) on the destinationAddress sent in the `transferTrancheTokensToEVM` function in PoolManager. This results in the ability for a user to burn tokens using an unguarded, public function in EVM A and expect it to be sent to a specific destination address in EVM B. However, the call in EVM B will be unexecutable, resulting in burnt tokens on EVM A and nothing in EVM B.

**Effected Contracts:** `src/PoolManager.sol`, `src/gateway/Gateway.sol`

## Vulnerability Details
### Reproduction Steps
1. EVM A user Alice calls `PoolManager.transferTrancheTokensToEVM` with a `destinationAddress` that is not in the member list for the specified tranche token on EVM B. (This could be by accident or the state of EVM B might be different than the expected state on EVM A)
2. The message from EVM A is successfully sent, tranche tokens from EVM A are burnt.
3. EVM B `Gateway.handle()` is called, decodes the payload, then calls `poolManager.handleTransferTrancheTokens(poolId, trancheId, destinationAddress, amount)`.
4. Because the destinationAddress in the payload is not a member of the tranche token member list, no tokens are minted, the function reverts at the following require: https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/PoolManager.sol#L272-L275.

### Proof of Concept Test
```solidity
// test/PoolManager.t.sol
function testTransferTrancheTokensToEVMFailsOnIncomingChain(
    uint64 poolId,
    bytes16 trancheId,
    string memory tokenName,
    string memory tokenSymbol,
    uint8 decimals,
    uint64 validUntil,
    address destinationAddress,
    uint128 amount,
    uint128 currency
) public {
    vm.assume(decimals <= 18);
    vm.assume(validUntil > block.timestamp + 7 days);
    vm.assume(destinationAddress != address(0));
    vm.assume(currency > 0);
    vm.assume(amount > 0);

    // Testing EVM A -> EVM B transferTranchTokens with an incorrect destinationAddress

    // Set up the EVM B (INCOMING) side **************************
    address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, currency);
    // On neither EVM A or B is the destinationAddress a member, or none at all.
    // I believe it's higher risk for one chain to have a destinationAddress that is a member, but the other not as it could lead to confusion.
    // DestinationAddress is not added as a member
    // homePools.updateMember(poolId, trancheId, destinationAddress, validUntil);
    homePools.updateMember(poolId, trancheId, address(this), validUntil);
    assertTrue(LiquidityPool(lPool_).checkTransferRestriction(address(0), address(this), 0));
    assertFalse(LiquidityPo
simply taking the ratio of maxDeposit and maxMint to calculate the deposit price  could allow manipulation
Audit: 2023-09-centrifuge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L557
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L566
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L569-L572


# Vulnerability details

## Impact
This could let users get unfair rates on deposit/redeem

## Proof of Concept
current deposit and redeem price calculations are vulnerable to manipulation 
The key parts are:
1. calculateDepositPrice:
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L557
2. calculateRedeemPrice:
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L566

The _calculatePrice function just takes a simple ratio between the deposit/withdraw and mint/redeem values:
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L569-L572


This means someone could manipulate the individual values to skew the price arbitrarily high or low.

For example, they could:
- Increase maxDeposit without increasing maxMint to lower deposit price
- Increase maxRedeem without increasing maxWithdraw to increase redeem price
- This could let them get unfair rates on deposit/redeem.

## Tools Used
manual
## Recommended Mitigation Steps
price calculation should rely on robust historical pricing data, not just the user's current limits


## Assessed type

Other
Arbitrary from in transferFrom
Audit: 2023-09-centrifuge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/InvestmentManager.sol#L294-L316
https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/InvestmentManager.sol#L312-L315


# Vulnerability details

## Impact

The transferFrom function is called in the handleExecutedDecreaseRedeemOrder function with address(escrow) as the from address. This indicates that the escrow account, rather than the sender of the current transaction, will be the one transferring the tranche tokens.

## Proof of Concept

This flaw appears when the sender of the current transaction is not the from address in the transferFrom function. As a result, an attacker may be able to move tokens from a user's account without that user's consent.

https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/InvestmentManager.sol#L312-L315

## Tools Used

manual review

## Recommended Mitigation Steps

The recommended way to call the transferFrom function is to use msg.sender as the from address. This ensures that the tokens are transferred from the account that is currently executing the transaction.

In your case, the line that you should change is:

require(LiquidityPoolLike(liquidityPool).transferFrom(address(escrow), user, trancheTokenPayout), "InvestmentManager/trancheTokens-transfer-failed");

Change it to:

require(LiquidityPoolLike(liquidityPool).transferFrom(msg.sender, user, trancheTokenPayout), "InvestmentManager/trancheTokens-transfer-failed");

This will fix the security vulnerability and ensure that the tranche tokens are transferred from the correct account.

Use the SafeTransferFrom function instead of the transferFrom function. The SafeTransferFrom function has additional security features that can help to prevent this vulnerability.



## Assessed type

ETH-Transfer
Token Transfer Assumption Vulnerability.
Audit: 2023-09-centrifuge-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact


**Vulnerability Overview:**

There exists a significant vulnerability related to the assumption of token transfers. Throughout the contract, there are multiple instances where token transfers are performed without thoroughly checking the return values of these transfers. This assumption can be highly problematic if the token transfer fails, and the contract does not handle the failure gracefully.

## Proof of Concept

From the assumption that the `transferFrom` function of the `lPool` (an ERC-20 token) will execute successfully. This function is used to transfer `trancheTokenAmount` from the escrow account to the user account. However, the code does not check the return value of this transfer, which can be a potential problem.

In the contract, the issue is exemplified by the following code snippet: **[Code Link](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L474-L477)**

```solidity
require(
    lPool.transferFrom(address(escrow), user, trancheTokenAmount),
    "InvestmentManager/trancheTokens-transfer-failed"
);
```

This code assumes that the `transferFrom` function of the `lPool` (an ERC-20 token) will execute successfully, transferring `trancheTokenAmount` from the `escrow` account to the `user` account. However, it does not check the return value of this transfer, which can be a potential problem.

These assumptions can be problematic if the token transfer fails, and the contract does not handle the failure gracefully. It could result in locked funds or incorrect state in the contract.

**Exploit Scenario**:

To illustrate the vulnerability, consider the following exploit scenario:

1. A malicious user deploys a malicious token contract, such as `MaliciousToken`, which always reverts transfer operations.

```solidity
// MaliciousToken.sol
contract MaliciousToken is ERC20 {
    function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
        revert("Transfer failed"); // Always reverts transfers
    }
}
```

2. The malicious user deploys an instance of the `InvestmentManager` contract and configures it to use the `MaliciousToken`.

```solidity
address maliciousTokenAddress = address(new MaliciousToken());
address escrow = msg.sender;
InvestmentManager investmentManager = new InvestmentManager(maliciousTokenAddress, escrow);
```

3. The malicious user then calls the `transferTrancheTokens` function with an arbitrary `user` address and an amount of tokens:

```solidity
investmentManager.transferTrancheTokens(address(0x123), 100);
```

4. Since the `MaliciousToken` always reverts transfers, the `require` statement in the `transferTrancheTokens` function will never succeed. However, the contract continues to execute, potentially causing inc
`LiquidityPool` contract tokens can be stolen by anyone
Audit: 2023-09-centrifuge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/LiquidityPool.sol#L295-L299
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/LiquidityPool.sol#L301-L305
https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/LiquidityPool.sol#L307-L311


# Vulnerability details

## Impact

- `LiquidityPool` contract has three functions (similar in design but supposed to be calling different functions on the share contract) that use low level calls to the share token address:

  1. `transfer` function that is supposed to transfer share tokens from the sender to any receiver.
  2. `transferFrom` function that is supposed to transfer share tokens from one account to any receiver by a spender.
  3. `approve` function that is supposed to approve a spenedr on the share tokens of the sender.

- But these contracts are using `call` low level opcode with a call data sent by the user (`msg.data`).

- So any malicious user can exploit any `LiquididtyPool` contract by sending a `msg.data` to approve himself (using any of the thre aforementioned functions;as they are different in names only but have the same design) on the share tokens of the contract; and then stealing it.

## Proof of Concept

[LiquidityPool.transferFrom function](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/LiquidityPool.sol#L295-L299)

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

[LiquidityPool.transfer function](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/LiquidityPool.sol#L301-L305)

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

[LiquidityPool.approve function](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/LiquidityPool.sol#L307-L311)

```solidity
    function approve(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));
    }
```

## Tools Used

Manual Testing.

## Recommended Mitigation Steps

Don't use a low level call with a `msg.data` predetermined by the user.


## Assessed type

call/delegatecall
Not normalizing the raw uint128 values to a common precision could cause a loss of precision in the price calculations.
Audit: 2023-09-centrifuge-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The raw values would produce an incorrect price due to mismatch in decimals.

## Proof of Concept
Not normalizing the raw uint128 values to a common precision could cause a loss of precision in the price calculations.
The relevant code is in _calculatePrice: https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L579-L581


It directly uses the currencyAmount and trancheTokenAmount raw values in the price calculation.
But these values could have different underlying precisions based on the token decimals:
• currencyAmount has precision of the pool's currency decimals
• trancheTokenAmount has precision of the pool's tranche token decimals
This could lead to loss of precision when multiplying or dividing these values to calculate the price.
For example:
• currencyAmount = 1,000,000,000 (8 decimals)
• trancheTokenAmount = 100,000 (5 decimals)

## Tools Used
Manual

## Recommended Mitigation Steps
the code should first normalize both values to a common precision (e.g. 18 decimals used in PRICE_DECIMALS) before doing the price calculation


## Assessed type

Other
Lack of explicit initialization checks in the constructor. contract initialization.
Audit: 2023-09-centrifuge-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

In the `InvestmentManager`, there's a vulnerability related to contract initialization, has been identified. This vulnerability poses a significant security risk to the contract and its users. The issue revolves around the absence of explicit initialization checks in the constructor, making it susceptible to exploitation.

## Proof of Concept

The vulnerability arises due to the reliance on the `escrow` and `userEscrow` addresses as indicators of contract initialization, without an accompanying boolean flag to explicitly confirm the initialization process. The constructor, shown below, initializes these two addresses but lacks a clear initialization check:

**[Code Link](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L88-L94)**
```solidity
constructor(address escrow_, address userEscrow_) {
    escrow = EscrowLike(escrow_);
    userEscrow = UserEscrowLike(userEscrow_);

    wards[msg.sender] = 1;
    emit Rely(msg.sender);
}
```

## Exploitation Scenario

1. An attacker deploys the `InvestmentManager` contract with malicious intentions.

2. The attacker sets the `escrow` and `userEscrow` addresses to their own malicious contract addresses, which are not compliant with the actual `EscrowLike` and `UserEscrowLike` interfaces.

```solidity
InvestmentManager manager = new InvestmentManager(address(new MaliciousEscrow()), address(new MaliciousUserEscrow()));
```

3. Since the constructor doesn't include an explicit `initialized` flag, the attacker successfully deploys the contract without any issues.

4. The attacker then calls sensitive functions on the `manager` contract, assuming that it's initialized. For example, they may call a function that transfers funds or executes other critical operations.

```solidity
function exploitManager() public {
    manager.transferFundsTo(address(this)); // Assuming there's a vulnerable function like this
}
```

5. The `exploitManager` function exploits the contract, as it doesn't check for proper initialization using an explicit `initialized` flag. The attacker can potentially execute malicious code or drain funds, causing unexpected behavior or financial losses.

## Tools Used
Vs

## Recommended Mitigation Steps

It is crucial to include an `initialized` flag within the contract. This flag should only be set to `true` once all necessary initialization steps are successfully completed. Sensitive functions in the contract should then check the state of this flag before executing to ensure that the contract is in the desired initialized state.

For example:

```solidity
bool public initialized;

constructor(address escrow_, address userEscrow_) {
    escrow = EscrowLike(escrow_);
    userEscrow = UserEscrowLike(userEscrow_);

    wards[msg.sender] = 1;
    emit
Reentrancy Vulnerability in `requestDeposit` and `requestRedeem` Functions of InvestmentManager Contract.
Audit: 2023-09-centrifuge-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

Description:

The InvestmentManager contract contains a high-severity reentrancy vulnerability in its `requestDeposit` and `requestRedeem` functions. These vulnerabilities could potentially allow an attacker to exploit the contract and manipulate the `escrow` contract's balance, leading to unexpected and malicious behavior.

**Details:**

The vulnerable functions as follows:

**In [`requestDeposit`](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L117-L141) Function:**  [136](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L136)

```solidity
function requestDeposit(uint256 _currencyAmount, address user) public auth {
    // ...
    SafeTransferLib.safeTransferFrom(currency, user, address(escrow), _currencyAmount);
    // ...
}

```

**In [`requestRedeem`](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L148-L172) Function:** [167](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/InvestmentManager.sol#L167)

```solidity
function requestRedeem(uint256 trancheTokenAmount, address user) public auth {
    // ...
    lPool.transferFrom(user, address(escrow), _trancheTokenAmount);
    // ...
}
```

Both functions involve external calls to other contracts (`SafeTransferLib.safeTransferFrom` in `requestDeposit` and `lPool.transferFrom` in `requestRedeem`). These external calls can be vulnerable to reentrancy attacks because the called contracts may contain arbitrary code.

## Proof of Concept

**Potential Attack Scenario:**

Consider the following steps:

**For `requestDeposit` Function:**

- An attacker deploys a malicious contract, `MaliciousContract`.

- The attacker deposits a certain amount of currency into `MaliciousContract`.

- The attacker then calls the `exploit` function within `MaliciousContract`.

- Inside the `exploit` function, the attacker first deposits funds into itself:

```solidity
escrow.deposit(address(this), msg.value);
```

- Next, the attacker calls the vulnerable `requestDeposit` function in the `InvestmentManager` contract:

```solidity
investmentManager.requestDeposit(msg.value, address(this));
```

Here's the critical point: The `exploit` function reenters the `requestDeposit` function. As a result, the attacker can recursively invoke the `requestDeposit` function multiple times, manipulating the `escrow` contract's balance with each call.

**For `requestRedeem` Function:**

A similar attack scenario can be constructed for the `requestRedeem` function, where an attacker ca
`Root` contract ward can deny himself leaving the contract inaccessible
Audit: 2023-09-centrifuge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/util/Auth.sol#L20-L23


# Vulnerability details

## Impact

- Centrifuge protocol authenticates addresses using the ward pattern; where addresses can be relied or denied to get access to some privilaged functionalities in the contracts that inherits the `Auth` contract.

- The `Root` contract is set as a ward on all other contracts.

- All the functionalities in the `Root` contract is accessible only by the contract wards; and as the `Root` inherits the `Auth` contract; the `Root` wards can rely (add)/deny (remove) any account to be ward in the `Root`.

- The problem arises **when there's only one ward in the `Root` contract**; and this ward denies (removes) himself from being a ward; which will leave the `Root` contract functionalities inaccessible: specially setting the contract pausability in cases of emergencies.

## Proof of Concept

[Auth.deny function](https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/util/Auth.sol#L20-L23)

```solidity
    function deny(address user) external auth {
        wards[user] = 0;
        emit Deny(user);
    }
```

## Tools Used

Manual Testing.

## Recommended Mitigation Steps

Add a variable to track the number of the current `Root` contract wards ; so when the `deny` function is called by a ward, it will check if the current number of `Root` wards is greater than a minimum threshold (2 wards for example) before removing any.


## Assessed type

Context
Vulnerability if trancheTokensPayout is 0 in the handleExecutedCollectInvest function.
Audit: 2023-09-centrifuge-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
This can artificially increase the deposit limits and misrepresent the total deposits

## Proof of Concept
The require check only verifies that currencyPayout is non-zero. There is no check on trancheTokensPayout.
This could allow an attacker to call the function with:
• currencyPayout > 0
• trancheTokensPayout = 0
In this case, it would:
• Increase the maxDeposit and maxMint values for the user, even though no tranche tokens were minted
• Emit the DepositProcessed event, falsely indicating a deposit occurred
This could artificially increase the deposit limits and misrepresent the total deposits.


## Tools Used
Manual

## Recommended Mitigation Steps
add a require to verify trancheTokensPayout is greater than 0


## Assessed type

Other