xanoutas/solidity-security-findings huggingface dataset:
Using `msg.value` in a loop allows the contract to be completely drained
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-L403


# Vulnerability details

## Impact
Using `msg.value` in a loop allows the contract to be completely drained of Ethereum.

## Proof of Concept
There is the private function `_transferOutAndCallV5()` that could be called either by `transferOutAndCallV5()` or `batchTransferOutAndCallV5()`. It takes the `TransferOutAndCallData` struct and uses it to create a swap from Ethereum to a token or from a token to another token. If, the fromAsset variable in the struct is `address(0)`, then a swap from Ethereum to a token will occur. This is the if statement we would end up in that scenario (skipped the event emission):
```solidity
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
        )
      );
      if (!swapOutSuccess) {
        bool sendSuccess = payable(aggregationPayload.target).send(msg.value);
        if (!sendSuccess) {
          payable(address(msg.sender)).transfer(msg.value);
        }
      }
}
```
The issue here is that this is one of the ways this function gets called:
```solidity
function batchTransferOutAndCallV5(
    TransferOutAndCallData[] calldata aggregationPayloads
  ) external payable nonReentrant {
    for (uint i = 0; i < aggregationPayloads.length; ++i) {
      _transferOutAndCallV5(aggregationPayloads[i]);
    }
  }
```
This uses `msg.value` in a loop essentially allowing for a user to send 1 Ether and if the loop is iterated 10 times, then that would count as 10 Ethereum as `msg.value` is the same for each iteration.

User can, for example, just specify himself as the target, send 1 Ethereum to the function, iterate it 10 times and steal 10 Ethereum of the contract.

While there is no documented way for Ethereum to be in the contract, it is still possible and I explained 4 ways for that to happen in my previous issue report regarding another way to completely drain the contract.

Also, if there is no ETH available in the contract, that is still unintended behavior that would lead to reverts in a lot of cases.

## Tools Used
Manual Review
## Recommended Mitigation Steps
Avoid using `msg.value` in a loop








## Assessed type

Loop
Fee on transfer tokens will make `_transferOutAndCallV5()` revert
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#L304-L389


# Vulnerability details

Thorchain has a whitelist of tokens it will use and among those are STA and PAXG which are fee on transfer tokens:
https://gitlab.com/thorchain/thornode/-/blob/develop/common/tokenlist/ethtokens/eth_mainnet_latest.json?ref_type=heads#L1942
https://gitlab.com/thorchain/thornode/-/blob/develop/common/tokenlist/ethtokens/eth_mainnet_latest.json?ref_type=heads#L1870

The `THORChain_Router.sol` correctly handles fee on transfer tokens in its `depositWithExpiry()` but it doesn't do so in `_transferOutAndCallV5()`.
The purpose of this function is that it allows a vault to send an asset (ERC20 or ETH) and swap it for another asset which will be sent to the recipient. It does so by transferring the asset to an aggregator which then initiates a transaction on a 3rd party protocol to do the swap.

However this will not work with fee on transfer tokens.


## Impact
Fee on transfer tokens will not work for `_transferOutAndCallV5()` as they will make it revert when the aggregator attempts to swap tokens using the swap router.

## Proof of Concept
Let's look at the portion of the `_transferOutAndCallV5()` that handles swaps from ERC20 to another ERC20. First we transfer a `fromAmount` amount of asset to the aggregator:
```solidity
      // 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
          )
        );
```
Then we call the `swapOutV5()` function on the aggregator with the same `fromAmount`:
```solidity
      (bool _dexAggSuccess, ) = aggregationPayload.target.call{value: 0}(
        abi.encodeWithSignature(
          "swapOutV5(address,uint256,address,address,uint256,bytes,string)",
          aggregationPayload.fromAsset,
          aggregationPayload.fromAmount,
...
        )
      );
```
In the audit repo there is an example contract of an aggregator and there we can see that `swapOutV5()` just calls `swapExactTokensForETH()` on a swap router (for example Uniswap)
```solidity
        (bool aggSuccess, ) = address(swapRouter).call(
          abi.encodeWithSignature(
            "swapExactTokensForETH(uint256,uint256,address[],address,uint256)",
            fromAmount,
```

Here is how fee on transfer tokens will cause issues:
1. Vault wants to swap 5 STA for DOGE so it calls `_transferOutAndCallV5()`.
2. Only 4.9 STA gets transfered to the aggregator because there was a 0.1 STA transfer fee.
3. The aggregator had no STA at first and now has 4.9 STA.
4. The aggregator calls `swapExactTokensForETH()` with 5 STA but doesn't have enough balance so the transaction reverts.

## Tools Used
Manual Review

## Recommended Mitigation Steps
To pre
Calling transferOutAndCallV5 function with fee on transfer token will fail
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#L391
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Router.sol#L364
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Aggregator.sol#L184
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Aggregator.sol#L203


# Vulnerability details

Any time `transferOutAndCallV5` function was called with a fee on transfer token like PAXG (Whitelisted) and the aggregator contract have 0 balance of the token, the transaction will fail because a value that is greater than the value received by the aggregator contract was used in calling `swapOutV5` function look at the scenario below.

1. Assume a user deposit 1 PAXG token with an intention of swapping to another token.
2. Bifrost will listen to deposit event and call `transferOutAndCallV5` function with a TransferOutAndCallData.fromAmount of 1 PAXG - fee because `depositWithExpiry` function pass (fromAmount - fee1) to deposit event.
3. The `transferOutAndCallV5` function will transfer the token again to aggregator contract. Another transfer fee will be deducted now we have (fromAmount - fee1 - fee2) in aggregator contract.
4. But (fromAmount - fee1) was used to call `swapOutV5` function of the aggregator contract, Its greater than what the contract recieved.
5. The `swapOutV5` function approve (fromAmount - fee1) to swapRouter. At the end the transaction will fail because the aggregator contract will not have enough token (it recieved fromAmount - fee1 - fee2 but approve fromAmount - fee1).

Check below codes also consider reading comments.

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

  // ..

  // transfer aggregationPayload.fromAmount token to aggregator but it will receive aggregationPayload.fromAmount - fee
  (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, // original aggregationPayload.fromAmount was used in the swapOutV5 instead of aggregationPayload.fromAmount - fee
  aggregationPayload.toAsset,
  aggregationPayload.recipient,
  aggregationPayload.amountOutMin,
  aggregationPayload.payload,
  aggregationPayload.originAddress
  ));


  // ..

}
```

```javascript
function swapOutV5(address
Logic error in _transferOutAndCallV5 internal function
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#L324


# Vulnerability details

The intention of the `_transferOutAndCallV5` internal function at the error line was to transfer the ETH to the recipient if the swapping failed but wrong parameter was used as `payable(aggregationPayload.target)` instead of `payable(aggregationPayload.recipient)`.

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

  // ..


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


  // ..

}
```

## Impact
Callers fund will always stay in the target(aggregator) contract when swapOutV5 failed

## Tools Used
Manual review

## Recommended Mitigation Steps
Add and remove the indicated lines below
```diff
function _transferOutAndCallV5(TransferOutAndCallData calldata aggregationPayload) private {

  // ..

  if (!swapOutSuccess) {
  -  bool sendSuccess = payable(aggregationPayload.target).send(msg.value);
  +  bool sendSuccess = payable(aggregationPayload.recipient).send(msg.value);
    if (!sendSuccess) {
      payable(address(msg.sender)).transfer(msg.value); // For failure, bounce back to vault & continue.
    }
  }


  // ..

}
```





## Assessed type

Error
User could get his funds locked using a particular type of 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#L26


# Vulnerability details

## Impact
Whenever a user deposits a fee on transfer token like `PAXG` or `STA` (both are whitelisted), there are a few ways he could get his funds locked into the contract.

## Proof of Concept
Fee on transfer tokens are tokens that charge a fee on transfer making recipients receive less than the sent amount.

This creates a few possible issues for the contract.

Imagine the following scenario:
1. User deposits `PAXG` (an allowed token) into the contract using `depositWithExpiry()` specifying that he wants to call `transferOutAndCallV5()` using the memo input parameter

This is the part of `_transferOutAndCallV5()` responsible for swapping from a token to another token:
```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
        )
      );
```
2. We transfer the `aggregationPayload.fromAmount` of the `PAXG` asset to the target aggregator contract and the target contract will receive less than the amount specified
3. We then call `swapOutV5` on the target contract with that same `fromAmount` and that contract will conduct the swap
4. Since the contract would have received less than the specified amount, this will definitely revert upon calling Uniswap or any other protocol they are using for the swap as the `fromAmount` specified is not actually available in the aggregator contract

Now, the funds will be locked in the aggregator contract until they are manually rescued.
## Tools Used
Manual Review
## Recommended Mitigation Steps
Handle such cases appropriate or disallow such tokens altogether.





## Assessed type

Under/Overflow
User can loss hsi funds in `_transferOutAndCallV5` function
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#L364


# Vulnerability details

The is `_transferOutAndCallV5` functions is called by the  by a THORChain vault when a user want to make a swap through the THORChainn.

See `_transferOutAndCallV5` function:

```solidity
function _transferOutAndCallV5(
    TransferOutAndCallData calldata aggregationPayload
  ) private {
   ...
    } 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,
          aggregationPayload.toAsset,
          aggregationPayload.recipient,
          aggregationPayload.amountOutMin,
          aggregationPayload.payload,
          aggregationPayload.originAddress
        ) <--------
      );

      emit TransferOutAndCallV5(
        msg.sender,
        aggregationPayload.target,
        aggregationPayload.fromAmount,
        aggregationPayload.toAsset,
        aggregationPayload.recipient,
        aggregationPayload.amountOutMin,
        aggregationPayload.memo,
        aggregationPayload.payload,
        aggregationPayload.originAddress
      );
    }
  }
```
[[Link]](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L304C3-L389C4)

This function is sending the tokens first to the `aggregationPayload.target` see first arrow, after checking that the transfer was successful the function then perform the call to make the swap if the aggregator fails (see second arrow above), the transaction should not revert.

The problem is that if the aggregator fails during the swap the funds are never refunded to the user and the event is emitted as is everything was successfully.

## Impact
If the swap in the aggregator contract fail the protocol is never refund the user.

## Proof of Concept

As we can see:

```solidity
functi
Passing multiple msg.value in the _transferOutAndCallV5 function can cause batchTransferOutAndCallV5 to fail
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#L310


# Vulnerability details


## Impact
In the function `_transferOutAndCallV5`, using `msg.value` when `fromAssets` is address(0) sends Ether to the `aggregationPayload.target`.
If this function is called multiple times within `batchTransferOutAndCallV5`, the repeated use of `msg.value` can cause the transaction to fail due to issues with transferring Ether multiple times.

## Proof of Concept

```solidity
File: https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L310
  function _transferOutAndCallV5(
    TransferOutAndCallData calldata aggregationPayload
  ) private {
    if (aggregationPayload.fromAsset == address(0)) {
      // call swapOutV5 with ether
      (bool swapOutSuccess, ) = aggregationPayload.target.call{
        value: msg.value // @audit 
      }(
        abi.encodeWithSignature(
          "swapOutV5(address,uint256,address,address,uint256,bytes,string)",
          aggregationPayload.fromAsset,
          aggregationPayload.fromAmount,
          aggregationPayload.toAsset,
          aggregationPayload.recipient,
          aggregationPayload.amountOutMin,
          aggregationPayload.payload,
          aggregationPayload.originAddress
        )
      );
      ...
  }
File: https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L397-L403
  function batchTransferOutAndCallV5(
    TransferOutAndCallData[] calldata aggregationPayloads
  ) external payable nonReentrant {
    for (uint i = 0; i < aggregationPayloads.length; ++i) {
      _transferOutAndCallV5(aggregationPayloads[i]); // @audit sends multiple msg.value
    }
  }

```

## Tools Used
Manual review

## Recommended Mitigation Steps
Use to determine the amount of Ether to send `aggregationPayload.fromAmount` instead of `msg.value` in `_transferOutAndCallV5` function.
Additionally, after processing all calls, validate if there is any remaining Ether and send it back to the `msg.sender` in `transferOutAndCallV5` & `batchTransferOutAndCallV5`.

```diff
  function _transferOutAndCallV5(
    TransferOutAndCallData calldata aggregationPayload
  ) private {
    if (aggregationPayload.fromAsset == address(0)) {
      // call swapOutV5 with ether
      (bool swapOutSuccess, ) = aggregationPayload.target.call{
-        value: msg.value
+        value: aggregationPayload.fromAmount
      }(
        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(aggregati
`THORChain_Router::batchTransferOutAndCallV5` function uses `msg.value` in a loop which makes the function unusable for batch transfers of ETH
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#L309-L311


# Vulnerability details

## Impact
This error makes the `THORChain_Router::batchTransferOutAndCallV5` function unusable for batch swapping ETH, because the full ETH value will be forwarded and depleted with the first transaction causing a revert. This breaks an important functionality for more efficient swaps of large amounts of assets.

## Tools Used
Slither

## Recommended Mitigation Steps
The `fromAmount` property on the `THORChain_Router::TransferOutAndCallData` should be used as the value to send when calling the `swapOutV5` function and the `THORChain_Router::batchTransferOutAndCallV5` should keep track whether the `msg.value` has been depleted on each loop iteration and revert if so.


## Assessed type

ETH-Transfer
THORChain_Router::batchTransferOutAndCallV5 reuses msg.value across a batch and will revert.
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-L403
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L304-L341


# Vulnerability details

## Impact
batchTransferOutAndCallV5 function attempts to use msg.value multiple times based on the batch size leading to revert as the contract will not have the balance.

## Proof of Concept
BatchTransferOutAndCallV5 is a batch processing function that allows for executing of swaps in a batch. The function accepts array of TransferOutAndCallData to perform the execution, refer below.   

```
   function batchTransferOutAndCallV5(
    TransferOutAndCallData[] calldata aggregationPayloads
  ) external payable nonReentrant {
    for (uint i = 0; i < aggregationPayloads.length; ++i) {
      _transferOutAndCallV5(aggregationPayloads[i]);
    }
  }
```
The TransferOutAndCallData has fromAsset and fromAmount along with toAsset for swapping. Refer to the below structure.

```
   struct TransferOutAndCallData {
    address payable target;
    address fromAsset;
    uint256 fromAmount;
    address toAsset;
    address recipient;
    uint256 amountOutMin;
    string memo;
    bytes payload;
    string originAddress;
  }
```
But, the bugging logic lies in the _transferOutAndCallV5, where if fromAsset is zero address, them from address is identified as Ether and the call is made on target contract with msg.value for swapOut.

The msg.value is the total Ether value passed into the batch function, but
the actual amount applicable should only be `fromAmount` passed in the structure.
As the array can have many instructions for swapping, if the first one uses msg.value, the subsequent calls for fromAsset == 0 will fail as there will not be enough ether left in the contract to perform the subsequent swaps.

The below logic is buggy due to the amount passed against the value parameter in the below target.call function.  

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

## Tools Used
Manual review

## Recommended Mitigation Steps
a) Modify the _transferOutAndCallV5 function to use aggregationPayload.fromAmount
instead of msg.value to support batch function flow as well.

b) In the entry functions, add validation to check if msg.value covers the fromAmount for all entries with fromAsset == 0. If not, revert the transaction.




## Assessed type

Payable