xanoutas/solidity-security-findings huggingface dataset:
`batchTransferOutAndCallV5` Will Always Revert if There is More Than One `ETH` Transfer
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#L310


# Vulnerability details

The `batchTransferOutAndCallV5()` function in `THORChain_Router` is designed to perform a batch of `transferAndCall` operations on target contracts, which execute swaps and transfers to Layer-1 chains. However, the `transferAndCall()` function is not designed to process multiple `ETH` transfers in a single transaction. Due to this issue, `batchTransferOutAndCallV5` will always revert if there is more than one `ETH` transfer.

## Impact

This issue results in the inability to process outbound batch transfers that include more than one `ETH` transfer on Thorchain by the vault.

## Proof of Concept

The `batchTransferOutAndCallV5` function is designed to perform multiple `transferAndCall` operations in a single transaction. It calls `_transferOutAndCallV5` for each element in the `aggregationPayloads` array.

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

[Link to the relevant code](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L397C1-L403C4)

In `_transferOutAndCallV5`, if the `fromAsset` is ETH (i.e., `address(0)`), it uses `msg.value` for the transfer. `msg.value` represents the total ETH sent with the transaction. In a loop, each iteration will attempt to use the entire `msg.value`, meaning the first ETH transfer will deplete the total `msg.value`, leaving no ETH for subsequent transfers.

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

[Link to the relevant code](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L310)

When `batchTransferOutAndCallV5` tries to perform more than one ETH transfer, the second and any subsequent transfers will fail because there is no ETH left. This will cause the entire transaction to revert.

## Tools Used

Manual Review

## Recommended Mitigation Steps

To fix this issue, use `aggregationPayload.fromAmount` instead of `msg.value` in the `_transferOutAndCallV5` function. This way, each transfer uses the specified amount for that particular transfer, preventing reverts due to insufficient ETH for subsequent transfers.

```diff
  function _transferOutAndCallV5(
    TransferOutAndCallData calldata aggregationPayload
  ) private {
    if (aggregationPayload.fromAsset == addres
`THORChain_Router::batchTransferOutAndCallV5` can be abused and user can drain contract funds.
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#L307-L341


# Vulnerability details

## Description

Even if a user is supposed to interact only with `depositWithExpiry`, the function `batchTransferOutAndCallV5` is external and callable by anyone. 
 
In the external function `batchTransferOutAndCallV5`, in the case of `fromAsset` being address(0), the function is calling `_transferOutAndCallV5` in a loop.

Then, it calls the target with the `msg.value` sent to the BATCH function instead of individually split it, a malicious user can trick the function by calling the batch function with multiple `TransferOutAndCallData` and `msg.value` and will actually get the contract ETH and could ultimately drain it.

The issue is that the msg.value received in `batchTransferOutAndCallV5` is global and not mapped to a single transfer in the loop, the value sent is therefore incorrect. It's not recommended to use msg.value in a loop.

See the POC below.

A couple of `TransferOutAndCallData` are instantiated. The `fromAsset` is address(0). The target for both is `alice`.

At the beginning, `alice` has 1 ether. She calls the function with a value of 1 ether but ends up with 2 ethers.

What happens behind the scene is actually the same `msg.value` is used twice in the function to call the target, and so the final balance will be 2 ethers.


## Impact

The integrity of the protocol is compromised.

## POC
```solidity
    function test_malicious() public {
      THORChain_Router.TransferOutAndCallData[] memory txs = new THORChain_Router.TransferOutAndCallData[](2);
      txs[0] = THORChain_Router.TransferOutAndCallData({
          target: payable(address(alice)),
          fromAsset: address(0),
          fromAmount: 1 ether,
          toAsset: address(0),
          recipient: address(0),
          amountOutMin: 0,
          memo: "",
          payload: "",
          originAddress: ""
        });

      txs[1] = THORChain_Router.TransferOutAndCallData({
          target: payable(address(alice)),
          fromAsset: address(0),
          fromAmount: 1 ether,
          toAsset: address(0),
          recipient: address(0),
          amountOutMin: 0,
          memo: "",
          payload: "",
          originAddress: ""
        });
      assertEq(alice.balance, 0 ether);
      vm.deal(alice, 1 ether);
      vm.deal(address(router), 20 ether);
      assertEq(alice.balance, 1 ether);
      vm.prank(alice);
      router.batchTransferOutAndCallV5{value: 1 ether}(txs);
      assertEq(alice.balance, 2 ether);
    }
```

## Recommended Mitigation

Here could be ways to handle this:

The function `_transferOutAndCallV5` should use the `fromAmount` and not `msg.value`.

The sum of fromAmount should be == to the `msg.value` passed to the `batchTransferOutAndCallV5` function, and the `_transferOutAndCallV5` function should use the `fromAmount` instead of the `msg.value`

```diff
Router transferOutAndCallV5:  Fee on transfer tokens lead to an error when swap and incorrect approval accounting
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#L347-L375


# Vulnerability details

## Impact
transferOutAndCallV5 won't work properly with fee on transfer tokens leading to a swap error and approval accounting discrepancies. 

## Proof of Concept

The vault calls `Router#transferOutAndCallV5` to Swap tokens and then send them to the recipient but is not handling correctly the fee of "fee on transfer tokens". The transferOutAndCallV5 first update the _vaultAllowance map, then transfer[!] the asset to the agregator and then execute the `swapOutV5` with the amount before the transfer. So when executes the `swapOutV5`, the aggregator has the amount after the transfer but the router is asking to do the swap with the amount before the transfer leading to an error. Also there will be an approval accounting problem because the aggregator will approve the DEX with more tokens than needed. 

## Tools Used

- VS code

## Recommended Mitigation Steps

Is recommended to check the balance of Token before and after the transfer to see how much tokens were received and swap only the received, also 
the contract already has a function that handles fee on tranfer tokens:  `safeTransferFrom` that returns the amount after the transfer, 

so i would recommend to do in line _transferOutAndCallV5#370

```javascript


    uint256 _startBalance = iERC20(aggregationPayload.fromAsset).balanceOf(aggregationPayload.target);

      // 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"
      );

      uint256 _finalAggregatorBalance = (iERC20(aggregationPayload.fromAsset).balanceOf(aggregationPayload.target) - _startBalance);

      // 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,
          _finalAggregatorBalance,
          aggregationPayload.toAsset,
          aggregationPayload.recipient,
          aggregationPayload.amountOutMin,
          aggregationPayload.payload,
          aggregationPayload.originAddress
        )
      );

```


## Assessed type

Token-Transfer
Msg-value-loop
Audit: 2024-06-thorchain-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
An attacker could potentially exploit the vulnerability by repeatedly calling the function that contains the loop with a small amount of Ether each time. This could lead to draining a significant amount of Ether from the contract, as the loop would transfer the Ether to the attacker's address each time it is executed. This could result in a loss of funds for the contract owner or users interacting with the contract.

## Proof of Concept
```
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 &amp; continue.
            }
        }

        // ... (rest of the code)
    } else {
        // ... (rest of the code)
    }
}

```

The vulnerability lies in the line

```
payable(address(msg.sender)).transfer(msg.value);
```

. This line transfers the entire

```
msg.value
```

back to the sender, which can lead to a reentrancy attack if the recipient contract is malicious.

To rectify this vulnerability, you should avoid transferring the entire

```
msg.value
```

in a single transaction. Instead, you can implement a pull-based 
payment system where the recipient contract must explicitly request the 
funds, and the contract can enforce limits on the amount that can be 
withdrawn.

## Tools Used
SET IN STONE : https://lab.setinstone.io

## Recommended Mitigation Steps
Here's an example of how you can modify the code:

```solidity
mapping(address =&gt; uint256) private pendingWithdrawals;

function _transferOutAndCallV5(
    TransferOutAndCallData calldata aggregationPayload
) private {
    if (aggregationPayload.fromAsset == address(0)) {
        // ... (existing code)

        if (!swapOutSuccess) {
            pendingWithdrawals[msg.sender] += msg.value; // Store the pending withdrawal amount
        }

        // ... (rest of the code)
    } else {
        // ... (rest of the code)
    }
}
batchTransferOutAndCallV5 cannot work correctly with ETH transfers
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

## Impact

`batchTransferOutAndCallV5` uses a loop of  `_transferOutAndCallV5` to batch multiple transactions together. 

However, `_transferOutAndCallV5` uses `msg.value` whenever `aggregationPayload.fromAsset == address(0)` and sends the entire `msg.value` amount to the target. This is a breaking issue in a batch, since there can be multiple transactions involving ETH, but only one `msg.value`. The first transaction where ETH is involved will take the entire ETH balance of the batch, breaking the function on the second loop. 
 
## Proof of Concept
Assume Batch with transaction A from Alice & B from Bob
A: aggregationPayload.fromAsset == address(0), msg.value = 1 ETH
B: aggregationPayload.fromAsset == address(0), msg.value = 1 ETH 

`batchTransferOutAndCallV5` is called with both transactions and a total msg.value of 2 ETH.

Loop 1: The function sends Alice `msg.value`, which is 2 ETH.
Loop 2: The function tries to send Bob the same amount, 2 ETH, but since there is no longer any ETH, the transaction reverts due to insufficient gas/eth.

## Tools Used

Manual review
## Recommended Mitigation Steps

Use aggregationPayload.fromAmount and not msg.value. 





## Assessed type

Loop
batch transfers will fail cause msg.value is used instead of aggregationPayload.fromAmount
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#L310


# Vulnerability details

# Vulnerability details

in _transferOutAndCallV5, when use msg.value when fromAssets is address(0) send funds to target.

when function called multiple times within a batch, the usage of msg.value mean transaction will fail cause all ether will be transferred with first function call

 
## Tools Used

## Recommended Mitigation Steps

Replace `msg.value` with `aggregationPayload.fromAmount`



## Assessed type

Other
If the fromAsset in the _transferOutAndCallV5 function is a fee-on token, the exchange may fail.
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#L347-L360


# Vulnerability details

## Impact
If the fromAsset in the _transferOutAndCallV5 function is a fee-on token, the exchange may fail.

## Proof of Concept
If fromAsset in the _transferOutAndCallV5 function is a charging token, [first transfer the funds to the target, and the amount is fromAmount](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L347-L360). Since it is a fee-based token, the amount received by target must be less than fromAmount.

Then call the target for redemption, and the [incoming parameter is fromAmount](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L364-L375). 
```solidity
      (bool _dexAggSuccess, ) = aggregationPayload.target.call{value: 0}(
        abi.encodeWithSignature(
          "swapOutV5(address,uint256,address,address,uint256,bytes,string)",
          aggregationPayload.fromAsset,
          aggregationPayload.fromAmount,
          aggregationPayload.toAsset,
          aggregationPayload.recipient,
          aggregationPayload.amountOutMin,
          aggregationPayload.payload,
          aggregationPayload.originAddress
        )
      );
```
[In the target, the swapOutV5 function also uses fromAmount for exchange.](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Aggregator.sol#L180-L214)
```solidity
        (bool aggSuccess, ) = address(swapRouter).call(
          abi.encodeWithSignature(
            "swapExactTokensForETH(uint256,uint256,address[],address,uint256)",
            fromAmount,
            amountOutMin,
            path,
            recipient,
            type(uint).max // Assuming using the maximum uint256 value as the deadline
          )
        );
        require(aggSuccess, "swapExactTokensForETH failed");
```

If there are no extra funds in the target, the exchange will fail. Even if there are excess funds, this will occupy other funds for exchange.

## Tools Used
manual

## Recommended Mitigation Steps
It is recommended to determine the fromAmount when exchanging based on the actual number of tokens transferred to the target.


## Assessed type

ERC20
Using msg.value inside a loop breaks function functionality
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#L397


# Vulnerability details

## Impact
When [`batchTransferOutAndCallV5()`](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L397) is called with 2 native currency transfers either contract native assets will get stolen (if there are any inside the contract) or the function will always revert.

## Proof of Concept
The root of the problem is that msg.value is used inside a loop. When this is done the contract receives for example 1 ETH but on each iteration of the loop msg.value is 1 ETH. 

This will cause the contract to try sending on each iteration 1 ETH by spending its own funds and not only the funds it had received. Since the contract is not expected to hold ETH the function will revert on the second loop iteration since there won't be enough ETH to be sent.

Example:
1. We call [`batchTransferOutAndCallV5()`](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L397) with [{fromAsset: address(0), fromAmount: 5e17...}, {fromAsset: address(0), fromAmount: 5e17...}] and we send 1e18 ETH with the function call.
2. On the first iteration the function will make a swap with the whole msg.value amount - thus using for the first recipient the whole 1 ETH despite the defined fromAmount value:
Snippet: [`link`](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L304-L389)

```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 the value here is 1 ETH
      }(
        abi.encodeWithSignature(
          "swapOutV5(address,uint256,address,address,uint256,bytes,string)",
          aggregationPayload.fromAsset,
          aggregationPayload.fromAmount, // @audit this value gets ignored when fromAsset == address(0)
          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.
        }
      }


      emit TransferOutAndCallV5(
        msg.sender,
        aggregationPayload.target,
        msg.value,
        aggregationPayload.toAsset,
[M-02] Incorrect call argument in `THORChain_Router::_transferOutAndCallV5`, leading to grief/steal of `THORChain_Aggregator`'s funds or DoS
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#L368


# Vulnerability details

## Impact
When transferring a token, which is of type fee-on-transfer, in `THORChain_Router::_transferOutAndCallV5`, the token is first deposited to the `THORChain_Aggregator` and then `THORChain_Aggregator::swapOutV5` is called with the same amount. The call will always revert if the `THORChain_Aggregator` does not have tokens or grief/steal (depending on the token) of `THORChain_Aggregator`'s tokens.
An example of a fee-on-transfer token that is in the whitelist is `PAXG` [see here](https://gitlab.com/thorchain/thornode/-/blob/develop/common/tokenlist/ethtokens/eth_mainnet_latest.json?ref_type=heads#L1870)
Loss of funds that are locked and waiting to be rescued in the `THORChain_Aggregator`

## Proof of Concept
To reproduce this, please add the following test fee-on-transfer token.
Create a file inside `ethereum/contracts` with the name `FeeOnTransferToken.sol`
Paste this inside:

```javascript
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;

interface iERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address, uint256) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address, uint256) external returns (bool);
    function transferFrom(address, address, uint256) external returns (bool);
}

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;
        return c;
    }
}

contract FeeOnTransferERC20Token is iERC20 {
    using SafeMath for uint256;

    string public name;
    string public symbol;
    uint256 public decimals = 18;
    uint256 public override totalSupply = 1 * 10 ** 6 * (10 ** decimals);
    uint256 immutable fee;

    mapping(address => uint256) public override balanceOf;
    mapping(address => mapping(address => uint256)) public override allowance;

    event Approval(address indexed owner, address indexed spender, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value);

    constructor(uint256 _fee) {
        balanceOf[msg.sender] = totalSupply;
        name = "Token";
        symbol = "TKN";
        emit Transfer(address(0), msg.sender, totalSupply);
        fee = _fee;
    }

    function mint(address to, uint256 value) public returns (bool success) {
        require(to != address(0), "address error")