xanoutas/solidity-security-findings huggingface dataset:
[M-03] Indirect use of `msg.value` in for loop, causing DoS of `THORChain_Router::batchTransferOutAndCallV5`
Audit: 2024-06-thorchain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-thorchain/blob/main/ethereum/contracts/THORChain_Router.sol#L310
https://github.com/code-423n4/2024-06-thorchain/blob/main/ethereum/contracts/THORChain_Router.sol#L324
https://github.com/code-423n4/2024-06-thorchain/blob/main/ethereum/contracts/THORChain_Router.sol#L326


# Vulnerability details

## Impact
The `msg.value` is indirectly used inside a for loop. This will make the `THORChain_Router::batchTransferOutAndCallV5` always revert when multiple transfers of ETH to tokens are made.
The whole `msg.value` is transferred to the target (`THORChain_Aggregator`) and for the next iteration there is no gas asset available, making the call always revert.

`THORChain_Router::batchTransferOutAndCallV5` will always revert if called with multiple `TransferOutAndCallData` that swaps gas asset for tokens

## Proof of Concept
To assert that the call will always revert add this test to the bottom of the `3_Batch.js`:

```javascript
it('Should fail on batch ETH for tokens', async function () {
  /*
    This will always fail because msg.value is used in the loop.
    It sends all the gas asset on the first iteration.
    On the second iteration the function is trying to send the msg.value again,
    but it's already been sent.
  */
  await truffleAssert.reverts(
    ROUTER.batchTransferOutAndCallV5(
      [
        [AGG.address, ETH, _1, TOKEN.address, USER1, '0', '', '0x', 'bc123'],
        [AGG.address, ETH, _1, TOKEN.address, USER2, '0', '', '0x', 'bc123'],
      ],
      { from: ASGARD, value: _2 }
    ),
    'Transaction reverted: function call failed to execute'
  );
});
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Inside `THORChain_Router::_transferOutAndCallV5` use the amount from the argument, not `msg.value`

```diff
 function _transferOutAndCallV5(TransferOutAndCallData calldata aggregationPayload) private {
        if (aggregationPayload.fromAsset == address(0)) {
            // call swapOutV5 with ether
-            (bool swapOutSuccess,) = aggregationPayload.target.call{value: msg.value}(
+            (bool swapOutSuccess,) = aggregationPayload.target.call{value: aggregationPayload.fromAmount}(
```

```diff
if (!swapOutSuccess) {
-   bool sendSuccess = payable(aggregationPayload.target).send(msg.value); // If can't swap, just send the recipient the gas asset
+   bool sendSuccess = payable(aggregationPayload.target).send(aggregationPayload.fromAmount); // If can't swap, just send the recipient the gas asset
   if (!sendSuccess) {
-       payable(address(msg.sender)).transfer(msg.value); // For failure, bounce back to vault & continue.
+       payable(address(msg.sender)).transfer(aggregationPayload.fromAmount); // For failure, bounce back to vault & continue.
   }
}
```

```diff
emit TransferOutAndCallV5(
  msg.sender,
  aggregationPayload.target,
-  msg.value,
+  aggregationPayload.fromAmount,
  aggregationPayload.toAsset,
  aggregationPayload.recipient,
```


## Assessed
Batch transfers will not work.
Audit: 2024-06-thorchain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L401
https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L307-L328


# Vulnerability details

## Impact
The protocol has implemented a function `batchTransferOutAndCallV5` that can do multiple transfers out. Instead of calling multiple times `transferOutAndCallV5`, you can call `batchTransferOutAndCallV5` function. However when more than one native transfers occur, this tx will fail.

## Proof of Concept
As can be seen, the function is `payable` and uses `for loop` to call the internal `_transferOutAndCallV5` function.

```solidity
  function batchTransferOutAndCallV5(
    TransferOutAndCallData[] calldata aggregationPayloads
  ) external payable nonReentrant {
    for (uint i = 0; i < aggregationPayloads.length; ++i) {
      _transferOutAndCallV5(aggregationPayloads[i]);
    }
  }
```

The problem is that that internal function uses `msg.value` instead of `aggreagationPayload.fromAmount`.

```solidity
  function _transferOutAndCallV5(
    TransferOutAndCallData calldata aggregationPayload
  ) private {
    if (aggregationPayload.fromAsset == address(0)) {
      // call swapOutV5 with ether
      (bool swapOutSuccess, ) = aggregationPayload.target.call{
        value: msg.value // @audit use of msg.value
      }(
        abi.encodeWithSignature(
          "swapOutV5(address,uint256,address,address,uint256,bytes,string)",
          aggregationPayload.fromAsset,
          aggregationPayload.fromAmount,
          aggregationPayload.toAsset,
          aggregationPayload.recipient,
          aggregationPayload.amountOutMin,
          aggregationPayload.payload,
          aggregationPayload.originAddress
        )
      );
      if (!swapOutSuccess) {
        bool sendSuccess = payable(aggregationPayload.target).send(msg.value);  // @audit use of msg.value
        if (!sendSuccess) {
          payable(address(msg.sender)).transfer(msg.value); // @audit use of msg.value
        }
      }

      emit TransferOutAndCallV5(
        msg.sender,
        aggregationPayload.target,
        msg.value,
        aggregationPayload.toAsset,
        aggregationPayload.recipient,
        aggregationPayload.amountOutMin,
        aggregationPayload.memo,
        aggregationPayload.payload,
        aggregationPayload.originAddress
      );
    } else {
      // ERC20 transfer
    }
  }
```

If there are more than 1 native transfers, the first one will use all of the `msg.value`. When it's time to execute the second one, the tx will fail, because there is no native tokens left.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use `aggreagationPayload.fromAmount` instead of `msg.value` when transferring native.


## Assessed type

ETH-Transfer
BatchTransferOutAndCallV5 iterate msg value
Audit: 2024-06-thorchain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Router.sol#L397-L403
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Router.sol#L304-L389


# Vulnerability details

### Summary 

In the current implementation of `THORChain_Router::batchTransferOutAndCallV5`, the logic is iterating over array of `TransferOutAndCallData`, but if there are more than one `TransferOutAndCallData` related to ether transfer, this will not work due to the current implementation.

### Proof of concept

If we take a look of the underlying function that is called by `THORChain_Router::batchTransferOutAndCallV5` during the iteration, `THORChain_Router::_transferOutAndCallV5`. We can see that during ether transfer, all of the msg.value will be swapped, but if there are more than one `TransferOutAndCallData` entries based on ether, the function will be revert all the time. You can take a look at he below code. 

```solidity
function _transferOutAndCallV5(TransferOutAndCallData calldata aggregationPayload) private {

	if (aggregationPayload.fromAsset == address(0)) {

		(bool swapOutSuccess, ) = aggregationPayload.target.call{ value: msg.value }(abi.encodeWithSignature(
		"swapOutV5(address,uint256,address,address,uint256,bytes,string)",
		aggregationPayload.fromAsset, aggregationPayload.fromAmount,
		aggregationPayload.toAsset, aggregationPayload.recipient,
		aggregationPayload.amountOutMin, aggregationPayload.payload,
		aggregationPayload.originAddress)
	
	);
```

### Tools Used
Manual review

### Impact

The current implementation causes the following issues:

1. **Transaction Reversion:** If there are multiple `TransferOutAndCallData` entries involving ether transfers in the `batchTransferOutAndCallV5` function, the transaction will revert every time.
2. **Inefficiency in Batch Operations:** Users attempting to batch multiple ether transfers will encounter failed transactions, rendering the batch processing feature ineffective.
3. **Loss of Funds:** Although the ether isn't lost permanently due to reversion, it can cause operational delays and confusion, especially in automated or large-scale systems.

### Recommended Mitigation Steps
The implementation of the current logic could look like this.

```diff
function batchTransferOutAndCallV5(TransferOutAndCallData[] calldata transferData) external payable {
    uint256 totalEtherRequired = 0;
    
    // Calculate total ether required
    for (uint256 i = 0; i < transferData.length; i++) {
        if (transferData[i].fromAsset == address(0)) {
            totalEtherRequired += transferData[i].fromAmount;
        }
    }

    require(msg.value == totalEtherRequired, "Insufficient ether sent");

    
    // Execute transfers
    for (uint256 i = 0; i < transferData.length; i++) {
        _transferOutAndCallV5(transferData[i]);
    }
}

function _transferOutAndCallV
Any Eth in router contract can be stolen by a malicious user
Audit: 2024-06-thorchain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-thorchain/blob/733dbe7cd7eef0dffc5e8a2d02e36bf74b196eff/ethereum/contracts/THORChain_Router.sol#L246-L250
https://github.com/code-423n4/2024-06-thorchain/blob/733dbe7cd7eef0dffc5e8a2d02e36bf74b196eff/ethereum/contracts/THORChain_Router.sol#L255-L259


# Vulnerability details

## Impact
Any ETH contained in the `THORChain_Router` either sent to the contract mistakenly or any other means can be stolen by a malicious vault. This would be achieved using the transferOutV5 or batchTransferOutV5 function.

## Proof of Concept
`THORChain_Router::transferOutV5` allows users transfer of ETH to other vaults. This calls `_transferOutV5` as can be seen below.
```
function transferOutV5(
    TransferOutData calldata transferOutPayload
  ) public payable nonReentrant {
    _transferOutV5(transferOutPayload);
  }
```
Within `_transferOutV5`, the amount entered in `transferOutPayload` is transferred to the recipient address.
```
function _transferOutV5(TransferOutData memory transferOutPayload) private {
    if (transferOutPayload.asset == address(0)) {
      //audit: This allows vault to send eth from this address rather than the calling vault
      bool success = transferOutPayload.to.send(transferOutPayload.amount); // Send ETH.
      if (!success) {
        payable(address(msg.sender)).transfer(transferOutPayload.amount); // For failure, bounce back to vault & continue.
      } .............................................
```

The amount of ETH sent is not dependent on the msg.value but rather on the vault's crafted calldata `transferOutPayload`. Therefore, by inputting the value of ETH in the THORChain_Router contract, the vault can send the ETH out of the Router contract to any recipient address which can be another contract controlled by them.


## Tools Used
Manual Review

## Recommended Mitigation Steps
1. For single and batch `transferOutV5`transactions, If token to transfer is ETH, then msg.value should be cached as a safeAmount and then substracted on every iteration. If safeAmount gets below 0, then msg.value sent with the transaction is exhausted and then transaction should continue or revert if msg.value is insufficient.

```diff
function _transferOutV5(TransferOutData memory transferOutPayload) private {
    if (transferOutPayload.asset == address(0)) {
+      int256 safeAmount = msg.value;
     bool success = transferOutPayload.to.send(transferOutPayload.amount); // Send ETH.
+      if(success) { 
+          safeAmount -= transferOutPayload.amount;
+          if(safeAmount < 0) {
+             return; 
          }
       }
      if (!success) {
        payable(address(msg.sender)).transfer(transferOutPayload.amount); // For failure, bounce back to vault & continue.
      }
```




## Assessed type

ETH-Transfer
Incorrect Ether Distribution in `batchTransferOutAndCallV5` Function
Audit: 2024-06-thorchain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L247-L253
https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L304-L389


# Vulnerability details


## Impact

The `batchTransferOutAndCallV5` function has a vulnerability that causes all the `msg.value` to be sent to the first target in the loop, leaving subsequent targets without the intended funds. This occurs because the `msg.value` is not correctly allocated per payload, leading to mismanagement of Ether distribution.

## Vulnerability Details

In the `batchTransferOutAndCallV5` function, the Ether transfer within the loop is not handled properly. When calling this function with multiple `aggregationPayloads`, the entire `msg.value` is sent to the first target, leaving subsequent targets without the intended funds.

```solidity
  function batchTransferOutAndCallV5(
    TransferOutAndCallData[] calldata aggregationPayloads
  ) external payable nonReentrant {
    for (uint i = 0; i < aggregationPayloads.length; ++i) {
      _transferOutAndCallV5(aggregationPayloads[i]);
    }
  }
```

```solidity
 function _transferOutAndCallV5(
    TransferOutAndCallData calldata aggregationPayload
  ) private {
    if (aggregationPayload.fromAsset == address(0)) {
      // call swapOutV5 with ether
      (bool swapOutSuccess, ) = aggregationPayload.target.call{
@>>        value: msg.value
      }(
        abi.encodeWithSignature(
          "swapOutV5(address,uint256,address,address,uint256,bytes,string)",
          aggregationPayload.fromAsset,
          aggregationPayload.fromAmount,
          aggregationPayload.toAsset,
          aggregationPayload.recipient,
          aggregationPayload.amountOutMin,
          aggregationPayload.payload,
          aggregationPayload.originAddress
        )
      );
      if (!swapOutSuccess) {
@>>        bool sendSuccess = payable(aggregationPayload.target).send(msg.value); // If can't swap, just send the recipient the gas asset
        if (!sendSuccess) {
@>>          payable(address(msg.sender)).transfer(msg.value); // For failure, bounce back to vault & continue.
        }
      }

```

## Proof of Concept

1. A user calls `batchTransferOutAndCallV5` with multiple aggregationPayloads, expecting each to receive a portion of the `msg.value`.

2. The entire `msg.value` is sent to the first payload's target.

3. Subsequent payloads do not receive any Ether, potentially causing failed transactions and unexpected behavior.

## Tools Used

Manual Review

## Recommended Mitigation Steps

To mitigate this issue, ensure that the correct amount of Ether is allocated for each payload. Instead of sending the entire `msg.value`, use `aggregationPayload.fromAmount` for the Ether transfer. This change ensures that each payload receives the appropriate amount of funds, preventing the exhaustion of the contract's Ether on the first target.

```diff
      (bool swapOu
ETH is sent to the aggregator contract instead of Recipient Address.
Audit: 2024-06-thorchain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Router.sol#L323-L328


# Vulnerability details

## Impact
The eth after swap is supposed to send to the Recipient Address if the aggregator fails to receive.

## Proof of Concept

It is clearly mentioned in the DOC that if the aggregator contract fails/revert when the eth is sent to it , it should repay the amount to the recipient address.
But in the  `_transferOutAndCallV5` we are sending eth to aggregator contract itself even though it has already failed.

`"Destination addresses should only be user-controlled addresses, not smart contract addresses.;"`
```solidity
function _transferOutAndCallV5(
    TransferOutAndCallData calldata aggregationPayload
  ) private {
    if (aggregationPayload.fromAsset == address(0)) {
      // call swapOutV5 with ether
      (bool swapOutSuccess, ) = aggregationPayload.target.call{
        value: msg.value
      }(
       ...
        )
      );
      if (!swapOutSuccess) {
        bool sendSuccess = payable(aggregationPayload.target).send(msg.value); // @audit should send to recipients address
        if (!sendSuccess) {
          payable(address(msg.sender)).transfer(msg.value); // For failure, bounce back to vault & continue.
        }
      }
    } 
      emit TransferOutAndCallV5(
       ...
      );
    }
}
```

Here instead the amount is passed to `aggregationPayload.target` instead of `aggregationPayload.recipient`.

## Tools Used
Manual review

## Recommended Mitigation Steps
Change `aggregationPayload.target` to `aggregationPayload.recipient`


## Assessed type

ETH-Transfer
SwapOutV5 : Fees are not deducted from the fromAmount  on `Fee On Transfer ERC20 Token's`  Transaction.
Audit: 2024-06-thorchain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Router.sol#L347-L389


# Vulnerability details

## Impact
While calling swapOut function of the aggregator contracts, we are passing the aggregationPayload.fromAmount which is greater than the real balance of the corresponding ERC20 token that we transferred from the Router initially and the swapOut function will fail as the user is expecting the real amount to be retrieved for swaping through that aggregator contract.

## Proof of Concept

Before calling the `swapOutV5` function in the aggregator we are transferring the required ERC20 tokens from the router contract to the aggregator.
The issue is we are not considering is the case of `fee On transfer` tokens.
As a result we are passing the same amount ,that we transferred initially to the aggregator , to the `swapOut` function without considering that fees are already deducted from the already sent amount.

```solidity
function _transferOutAndCallV5(
    TransferOutAndCallData calldata aggregationPayload
  ) private {
    if (aggregationPayload.fromAsset == address(0)) {
      ....
     
    } else {
      _vaultAllowance[msg.sender][
        aggregationPayload.fromAsset
      ] -= aggregationPayload.fromAmount; // Reduce allowance

      // send ERC20 to aggregator contract so it can do its thing
      (bool transferSuccess, bytes memory data) = aggregationPayload
        .fromAsset
        .call(
          abi.encodeWithSignature(
            "transfer(address,uint256)",
            aggregationPayload.target,
            aggregationPayload.fromAmount
          )
        );

      require(
        transferSuccess && (data.length == 0 || abi.decode(data, (bool))),
        "Failed to transfer token before dex agg call"
      );

      // add test case if aggregator fails, it should not revert the whole transaction (transferOutAndCallV5 call succeeds)
      // call swapOutV5 with erc20. if the aggregator fails, the transaction should not revert
      (bool _dexAggSuccess, ) = aggregationPayload.target.call{value: 0}(
        abi.encodeWithSignature(
          "swapOutV5(address,uint256,address,address,uint256,bytes,string)",
          aggregationPayload.fromAsset,
          aggregationPayload.fromAmount, //@audit fee on transfer token will cause this transaction to not happen as expected.FromAmount is greater than aggregator balance at the time of this call.
          aggregationPayload.toAsset,
          aggregationPayload.recipient,
          aggregationPayload.amountOutMin,
          aggregationPayload.payload,
          aggregationPayload.originAddress
        )
      );

      emit TransferOutAndCallV5(
        ....
      );
    }
}
```
As we can see same amount ( `aggregationPayload.fromAmount` ) is passed for swapOutV5 which is incorrect in the case of Fee on transfer ERC20 tokens.

## Tools Used
Manual review

## Recommended Mitigation Steps
Calculate
The router contract is not compatible with fee-on-transfer tokens
Audit: 2024-06-thorchain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L200-L206
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L220-L237
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L347-L355


# Vulnerability details

The router contract is used to deposit funds, which are then transferred out, and the appropriate event should be emitted to be processed by the ThorChain network. However, there is an oversight in some of the functions which emit such an event when the token used is an ERC20 with fee-on-transfer.

The functions which are not compatible with the fee-on transfer are: `transferOut()`, `_transferOutV5()`, and  `_transferOutAndCallV5()`. If we look at their implementation, we can see that the actual transferred value is not checked.

```solidity
  function transferOut(
    address payable to,
    address asset,
    uint amount,
    string memory memo
  ) public payable nonReentrant {
    uint safeAmount;
    if (asset == address(0)) {
      ...
    } else {
      _vaultAllowance[msg.sender][asset] -= amount; // Reduce allowance
      (bool success, bytes memory data) = asset.call(
        abi.encodeWithSignature("transfer(address,uint256)", to, amount)
      );
      require(success && (data.length == 0 || abi.decode(data, (bool))));
      safeAmount = amount;
    }
    emit TransferOut(msg.sender, to, asset, safeAmount, memo);
  }
```

```solidity
  function _transferOutV5(TransferOutData memory transferOutPayload) private {
    if (transferOutPayload.asset == address(0)) {
      ...
    } else {
      _vaultAllowance[msg.sender][
        transferOutPayload.asset
      ] -= transferOutPayload.amount; // Reduce allowance

      (bool success, bytes memory data) = transferOutPayload.asset.call(
        abi.encodeWithSignature(
          "transfer(address,uint256)",
          transferOutPayload.to,
          transferOutPayload.amount
        )
      );

      require(success && (data.length == 0 || abi.decode(data, (bool))));
    }

    emit TransferOut(
      msg.sender,
      transferOutPayload.to,
      transferOutPayload.asset,
      transferOutPayload.amount,
      transferOutPayload.memo
    );
  }
```

```solidity
  function _transferOutAndCallV5(
    TransferOutAndCallData calldata aggregationPayload
  ) private {
    if (aggregationPayload.fromAsset == address(0)) {
      ...
    } else {
      _vaultAllowance[msg.sender][
        aggregationPayload.fromAsset
      ] -= aggregationPayload.fromAmount; // Reduce allowance

      // send ERC20 to aggregator contract so it can do its thing
      (bool transferSuccess, bytes memory data) = aggregationPayload
        .fromAsset
        .call(
          abi.encodeWithSignature(
            "transfer(address,uint256)