xanoutas/solidity-security-findings huggingface dataset:
Due to the use of `msg.value` in for loop, anyone can drain all the funds from the `THORChain_Router` contract
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#L400-L402
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L309-L311
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L324


# Vulnerability details

The functions [transferOutAndCallV5()](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L391-L395) and [batchTransferOutAndCallV5()](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L397-L403) both internally invoke [_transferOutAndCallV5()](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L304). This function, in turn, calls the `swapOutV5()` function on the `aggregationPayload.target`, which should be the `THORChain_Aggregator` contract.

However, the `aggregationPayload` is a struct passed as a parameter through either [transferOutAndCallV5()](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L391-L395) or [batchTransferOutAndCallV5()](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L397-L403). This allows anyone to set the `aggregationPayload.target` address to their preference without any validation.

When a low-level call to `aggregationPayload.target` is made, the return value `swapOutSuccess` is checked. If it's false, a fallback logic attempts to `send` the `msg.value` to the target. If this also fails, the `msg.value` is refunded to the `msg.sender`.

The issue arises when [batchTransferOutAndCallV5()](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L397-L403) is called. It loops through the `aggregationPayloads` array and passes each element to [_transferOutAndCallV5()](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L304), which then sends the `msg.value` multiple times to either the `target` address or `msg.sender`.

```solidity
  function batchTransferOutAndCallV5(
    TransferOutAndCallData[] calldata aggregationPayloads
  ) external payable nonReentrant {
    for (uint i = 0; i < aggregationPayloads.length; ++i) {
@>    _transferOutAndCallV5(aggregationPayloads[i]);
    }
  }
  
  function _transferOutAndCallV5(
    TransferOutAndCallData calldata aggregationPayload
  ) private {
    if (aggregationPayload.fromAsset == addre
`_transferOutAndCallV5`  function is incompatible for the Fee-on-Transfer tokens
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#L368


# Vulnerability details

The protocol have a [whitelist](https://gitlab.com/thorchain/thornode/-/blob/develop/common/tokenlist/ethtokens/eth_mainnet_latest.json?ref_type=heads) for the tokens, the whitelist includes some [Fee-on-Transfer](https://github.com/d-xo/weird-erc20?tab=readme-ov-file#fee-on-transfer) tokens as well like `PAXG`. The `ThorChain_Router` contract's functions should be compatible with the Fee-on-transfer tokens but they are not. 

The `_transferOutAndCallV5` function are implementetd to allow users to make swaps with external liquidity, it uses an aggregator to create the swaps possible:
![TC_Aggr-Aggregator drawio](https://cdn.discordapp.com/attachments/1245809305505104035/1248702700770558064/TC_Aggr-Aggregator.drawio.png?ex=666a8f43&is=66693dc3&hm=34ef894fdcf108f3a9fd66e6f130ee26341139928339d92a9ce367c05c838edc&)


As we can see, the `_transferOutAndCallV5` function first transfers the tokens to the `aggregator` and then calls the `aggregator`'s `sawpOutV5()` to complete the swap.
```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"
      );

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

The problem is that the protocol tries to swap the same amount of tokens it sent to the aggregator before the swap `aggregationPayload.fromAmount`. Which means this will revert due to insufficient balance when the token will be a Fee-on-transfer token:
```solidity
      (bool transferSuccess, bytes memory data) = aggregationPayload
        .fromAsset
        .call(
          abi.encodeWithSignature(
            "transfer(address,uint256)",
            aggregationPayload.target,
@>          ag
`batchTransferOutAndCallV5` might revert if there are more than 2 calls, which transfer ETH
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#L309-L311


# Vulnerability details

## Impact
If `batchTransferOutAndCallV5` is called and 2 or more of those are transfers of native ETH, the tx will revert, since `batchTransferOutAndCallV5` uses a for-loop and `msg.value` is preserved through all iterations of the loop.

## Proof of Concept
A user wants to make multiple swap via the router and `batchTransferOutAndCallV5` function.
1. User calls `batchTransferOutAndCallV5` with an array of 2 elements both of which have `aggregationPayload.fromAsset` set as `address(0)`
2. The amount of ETH can be `X` for call1 and `Y` for call2 - which means the caller needs to send `X + Y` amount of ETH alongside the tx
3. Since on the very first iteration inside the `_transferOutAndCallV5()` the call will forward all `X + Y` ETH to the aggregator (L309-L311)
4. On the next iteration it tries to do the same, but the ETH amount inside the router contract is already 0

## Tools Used
Manual review

## Recommended Mitigation Steps
Use `value: aggregationPayload.fromAmount` instead of `value: msg.value`


## Assessed type

Other
The new `_transferOutAndCallV5()` function is not compatible with fee-on-transfer and rebase tokens
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#L342-L375


# Vulnerability details

## Impact

1. The function can't ever be executed as intended.

2. Potential loss of user funds

## Proof of concept

The `_transferOutAndCallV5()` is an internal function that is used in `transferOutAndCallV5()` and `batchTransferOutAndCallV5()` which allows a THOR Vault to transfer an amount of native coin or ERC20 tokens to a recipient by first swapping the corresponding amount using an aggregator.

Here is the code snippet responsible for swapping 1 asset to another before sending the output asset to the recipient :

[https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L342-L375](https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L342-L375)

```solidity
_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,
        aggregationPayload.toAsset,
        aggregationPayload.recipient,
        aggregationPayload.amountOutMin,
        aggregationPayload.payload,
        aggregationPayload.originAddress
    )
);
```

First, the `THORChain_Router` contract (that holds the asset to be swapped) transfers the input tokens (`aggregationPayload.fromAsset`) to the aggregator (`aggregationPayload.target`) using a low-level call.

At this point, the aggregator holds the input assets and new low-level call is made to the aggregator to swap these tokens to the output assets using `swapOutV5()`.

When dealing with fee-on-transfer and rebase tokens, the actual number of tokens received after a `transfer()` might not be equal to the amount of tokens sent.

This means the call to [`swapOutV5()`](https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Aggregator.sol#L144-L216) on the aggregator will most likely fail because it will attempt to swap an amount of tokens that exceeds balance of the `THORChain_Router`.

However, t
`batchTransferOutAndCallV5` function is broken, it will not work in some cases and in some cases it will emit wrong event values.
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#L310
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Router.sol#L333


# Vulnerability details

The `batchTransferOutAndCallV5` is implemented to allow users to use the `_transferOutAndCallV5()` in a batch call. The `_transferOutAndCallV5()` uses `msg.value` very irresponsibly which will result in the batch function not working or emitting wrong events.
As we can see that the function when calls the target it passes the whole `msg.value`, so when this function will be called in a batch to transfer ETH to multiple addresses then this will simply revert, as it in the first loop call it will consume all the `msg.value`, no ethers will be left for the 2nd loop iteration.

Then the function emits the `msg.value` as an event as well, which is totally wrong as explained above and will be breaking an important protocol's variant: "Only valid events (should be) emitted from the Router contract"
```solidity
  function _transferOutAndCallV5(TransferOutAndCallData calldata aggregationPayload) private {
    if (aggregationPayload.fromAsset == address(0)) {
      (bool swapOutSuccess, ) = aggregationPayload.target.call{
@>      value: msg.value ///@audit full msgValue used in case of batch!
      }(
        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.
        }
      }

      emit TransferOutAndCallV5(
        msg.sender,
        aggregationPayload.target,
@>      msg.value, ///@audit-issue M wrong event emited! this should be aggpayload.fromAmount not msg.value, in the case of batch functionality this can be provlematic
        aggregationPayload.toAsset,
        aggregationPayload.recipient,
        aggregationPayload.amountOutMin,
        aggregationPayload.memo,
        aggregationPayload.payload,
        aggregationPayload.originAddress
      );
    } else {
      .....
    }
  }
```

## Impact
- Broken Functionality
- Wrong Events Emissions 

## Proof of Concept
```js
    it("Should reverts batchTransferOutAndCallV5", async function () {

      await truffleAssert.reverts(ROUTER.batchTransferOutAndCallV5(
        [
          [
            AGG.address,
            ETH,
            _1,
`_transferOutAndCallV5()` is incompatible with fee on transfer tokens
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-L355


# Vulnerability details

## Impact
The `_transferOutAndCallV5()` is supposed to work with virtually any kind of tokens, but it will most likely fail every single time when a swap with fee on transfer tokens is made. Before calling the aggregator, the tokens are already sent to the contract. With non-weird ERC20 tokens there will be no issues, but if fee on transfer tokens are used, that might not be the case.

## Proof of Concept
1. A user deposits 100 tokens through `depositWithExpiry()` (Let's assume for the example that the fee is 10% -> the `_vaultAllowance` mapping will then store 90 for that vault and token)
2. The vault calls `transferOutAndCallV5()`, the balance will be properly reduced
3. When the transfer is supposed to happen, not 90, but 81 tokens will be transfered to the aggregator (90 tokens * 90% == 81 tokens)
4. During the `swapOutV5`, unless there are more tokens sent by mistake, the aggregator will try to make a call to the Uniswap router and fail -> it approves the Uniswap router for 90 tokens, but has only 81

Note: The revert happens in the aggregator, but the `THORChain_Router` contract is at fault. The reason why: even if the issue is fixed inside the `THORChain_Router` contract, there will be no reverts inside the aggregator. However, because the aggregator also does not account for tokens with fees on transfers, the revert will happen inside the Uniswap router contract. 

As a summary, both contracts will have issues with fee on transfer tokens and even though the `-transferOutAndCallV5()` does not revert, because of the lack of validation, a revert will happen in the following call to the aggregator and same for the aggregator and the Uniswap router

## Tools Used
Manual review

## Recommended Mitigation Steps
Make sure to use the same pattern as inside `safeTransferFrom()` - checking what is the balance of the aggregator before and after sending the tokens prior to calling `swapOutV5`





## Assessed type

Other
`batchTransferOutAndCallV5` will be unusable due to msg.value in a loop type situation.
Audit: 2024-06-thorchain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-thorchain/blob/e5ae503d0dc2394a82242be6860eb538345152a1/ethereum/contracts/THORChain_Router.sol#L401
https://github.com/code-423n4/2024-06-thorchain/blob/e5ae503d0dc2394a82242be6860eb538345152a1/ethereum/contracts/THORChain_Router.sol#L307-L311


# Vulnerability details

## Impact

Calling `batchTransferOutAndCallV5` function and transferring ETH in a batch will be impossible due to the fact that the internally called `_transferOutAndCallV5` nakedly handles msg.value, causing that the function will always fail after the first ETH transfer iteration.

## Proof of Concept

1. Context

When `msg.value` is handled in a loop, the entire `ETH` sent is transferred in the first loop, so upon subsequent loops, this time the second, the amount of `ETH` sent by the sender left will now be zero while the function attempts to send the `msg.value` again but this time, the transaction will fail and revert breaking the `batchTransferOutAndCallV5` functionality.

2. Bug location

Taking a look at the `batchTransferOutAndCallV5` function, the `_transferOutAndCallV5` function is called in a for loop while looping through the `aggregationPayloads`.

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

The `_transferOutAndCallV5` function queries if the asset is ETH, upon which `msg.value` is sent to the `aggregationPayload` target.

```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
        )
      );
```
3. Final effect

As has been established in the impact section, after the first iteration of sending `ETH` to the `aggregationPayload` target, further attempts to send `ETH` to the next `aggregationPayload` targets will fail as `msg.value` will be attempted to be sent again, causing a reversion as balance is not enough. This breaks the `batchTransferOutAndCallV5` function, rendering it useless.

If for instance `batchTransferOutAndCallV5` is called to transfer 2 `ETH` to 2 targets at 1 `ETH` each, the first iteration will send msg.value == which is 2 `ETH` to the first target in the first loop. And as we have established the `msg.value` will always remain at 2 `ETH`
Users will be denied from using a particular protocol functionality under a certain case
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#L304-L389


# Vulnerability details

## Impact
Users will be denied from using the swapping functionality under a certain case

## Proof of Concept
Users can call `depositWithExpiry()` with a memo of their choice signalizing the intent of their deposit, for example swapping. Then, the function responsible for handling their intent gets called after scanning the event and confirming the validity of the transaction. However, there is a particular scenario that would DoS the users from using the swapping functionality and lock their tokens until the owner calls `rescueFunds()` in `THORChain_Aggregator.sol`.

This is part of the `_transferOutAndCallV5()` function responsible for swapping (just the else statement that is responsible for token to token swap, also ignored the event emission):
```solidity
_vaultAllowance[msg.sender][aggregationPayload.fromAsset] -= aggregationPayload.fromAmount;

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

      (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
        )
      );
}
```
First, the vault allowance gets decremented and the tokens are transferred to the target contract. Then, `swapOutV5()` gets called on the target contract. Here is a part the current implementation for it:
```solidity
      path[0] = fromAsset;
      path[1] = WETH;
      safeApprove(fromAsset, address(swapRouter), 0); // best practice
      safeApprove(fromAsset, address(swapRouter), fromAmount);

      if (payload.length == 0) {
        // no payload, process without wasting gas
        // UniV2 transfers from 'msg.sender', so we need a low-level `.call` to change the msg.sender from the target's perspective
        (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
          )
        );
```
It swaps the tokens for ETH. The is