xanoutas/solidity-security-findings huggingface dataset:
Unsuccessful swap could lead to loss of ERC20 tokens
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#L362-L364


# Vulnerability details

## Impact
Inside transferOutAndCallV5 there is a call to the target contract with the intention to swap ETH/token for another token, in the case of ERC20 if the swap fails - router won't revert the transaction and because router has already sent tokens to the target contract - this would result in a loss of funds.

## Proof of Concept
There is no check for the `_dexAggSuccess` value, and the comment 'if the aggregator fails, the transaction should not revert', this means that unsuccessful swap would result in simply sending your tokens to the `aggregationPayload.target` contract instead of doing a swap.

## Tools Used
Manual review.

## Recommended Mitigation Steps
Add a swap execution check and revert if it fails.


## Assessed type

ERC20
If swap fails a vault will lose the ETH it has sent
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

The `_transferOutAndCallV5()` allows a vault to send ETH and swap it for another token which will be sent to the recipient. The implementation has a case in which if the swap fails it should send the ETH directly to the recipient. However it sends the ETH to the aggregator instead.

## Impact
The recipient will not receive any ETH and the vault will lose funds.

## Proof of Concept
Here is a snippet from `_transferOutAndCallV5()`:
```solidity
      (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
```
As you can see it first calls `swapOutV5()` on the `aggregationPayload.target` which is the address of the aggregator that swaps tokens in a 3rd party protocol.

If the swap fails we enter the if statement below and according to the comment on the last line we should send the recipient the gas asset (ETH).

However as you can see it sends it to `aggregationPayload.target` which is the address of the aggregator not the recipient.

## Tools Used
Manual Review

## Recommended Mitigation Steps
```diff
      if (!swapOutSuccess) {
-       bool sendSuccess = payable(aggregationPayload.target).send(msg.value);
+       bool sendSuccess = payable(aggregationPayload.recipient).send(msg.value);
```


## Assessed type

ETH-Transfer
A malicious user can steal money out of the vault and other users
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#L466-L483


# Vulnerability details

## Impact
A malicious user can steal money out of other users and the vault when a rebasing token like AMPL is used. That particular token is whitelisted as seen here: https://gitlab.com/thorchain/thornode/-/blob/develop/common/tokenlist/ethtokens/eth_mainnet_latest.json?ref_type=heads

## Proof of Concept
The AMPL token, in particular, has a `_gonsPerFragment` variable that is used to change the balances of other users. The goal of that is to keep a stable price by manipulating the supply. This creates a huge issue for this particular protocol. The cause of the issue is the fact that upon a user moving his funds to another router, the router is being approved based on the amount of tokens he deposited. However, since that is a rebasing token, that amount can change and allow him to steal funds as explained below.

Imagine the following scenario (will keep it very simplistic):
1. A malicious actor deposits 1000 tokens when the `_gonsPerFragment` is 1 and sets his address as the vault address. The balance of AMPL token in the contract is now 1000 and the allowance he has for his own address is also 1000.
2. `_gonsPerFragment` is now 2 after the `rebase()` function was called on the AMPL contract, he could even frontrun it
3. He calls `transferAllowance()` with his own malicious router, address of the vault is not important, `AMPL` as the asset and 1000 tokens as the amount
4. This calls `_routerDeposit()`, takes out his 1000 tokens from the allowance, approves the malicious router contract for 1000 tokens. The thing here is that the router should not take the tokens, it is just approved. This is how the malicious router can look like (`depositWithExpiry()` is just a blank function):
```solidity
contract MaliciousRouter {
    function depositWithExpiry(address, address, uint256, string calldata, uint) public {}

    function steal(uint256 amount, address from, address to, address target) public {
        (bool ok, ) = target.call(abi.encodeWithSignature("transferFrom(address,address,uint256)", from, to, amount));
        if (!ok) revert();
    }
}
```
5. The balance of the contract is 1000 / 2 = 500 (as `_gonsPerFragment` is 2, take a look at the `AMPL` contract if not knowledgeable of how it works)
6. Now, a legit user deposits 1000 tokens into a legit vault, now balance of the contract is `(1000 + 1000 * 2) / 2 = 1500` and the `_vaultAllowance` for the vault is `1500 - 500 = 1000`
7. The malicious user can now take some of those funds as he is approved for 1000 tokens, he calls the `steal()` function as specified in the malicious router contract above with his address as the receiver and 1000 tokens as the amount
8. The allowance check passes (`1000 - 1000 = 0`) and he successfully claims (`2000 / 2 = 1000 tokens`)
9. The balance of the contract is now jus
Without checking _dexAggSuccess, there is a risk of losing transferred assets to the `aggregationPayload.target`
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#L364


# Vulnerability details


## Impact
After `swapOutV5` fails, the assets are transferred to the aggregator contract.
Since anyone can call the `THORChain_Aggregator.swapOutV5` function, an attacker could exploit this by swapping the failed assets by changing the `recipient`, `amountOutMin`, or `toAsset`.

## Proof of Concept
An attacker can monitor transactions for `swapOutV5` and initiate a frontrun to cause the swap to fail.
Once the swap fails and the assets are transferred to the aggregator, the attacker can call `swapOutV5` to steal the assets without transferring the asset.
```solidity
File: https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L364
  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
          )
        ); // @audit fromAsset is transferred here

      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}(  // @audit dexAgg fails what happened? asset already transferred to .target?
        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_Aggregator.sol#L144-L216
  function swapOutV5(
    address fromAsset,
    uint256 fromAmount,
    address toAsset,
    address recipient,
    uint256 amountOutMin,
    bytes memory payload,
    string memory originAddress
  ) public payable nonReentrant {
    ...
  }

```

## Tools Used
Manual review

## Recommended Mitigation Steps
Use token approvals instead of direct transfers for `swapOutV5` and update the relative contract.
After it fails, reset the
If the swapOut fails, the Ether should be sent to the recipient instead of the aggregator
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
In the _transferOutAndCallV5 function, when a swap cannot be completed, the Ether should be sent to the recipient.
However, it is incorrectly sent to the aggregator, resulting in the user's funds being locked.

## Proof of Concept

```solidity
File: https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L324
  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 // @audit should be sent to the recipient
        if (!sendSuccess) {
          payable(address(msg.sender)).transfer(msg.value); // For failure, bounce back to vault & continue.
        }
      }
    ...
  }
```

## Tools Used
Manual review

## Recommended Mitigation Steps
Sends the ether to the recipient instead of the aggregator.

```diff
File: https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L304-L328
  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
+        bool sendSuccess = payable(aggregationPayload.recipient).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.
TransferOutAndCall event should not be emitted if the `ethSuccess` is false
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#L280
https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L326


# Vulnerability details


## Impact
If a swap and the subsequent transfer to the recipient both fail, the Ether is returned to the vault.
In this scenario, the TransferOutAndCall was not successful, so the TransferOutAndCall event should not be emitted.
This allows the vault to recognize the failure and attempt the call again later.

## Proof of Concept

```solidity
File: https://github.com/code-423n4/2024-06-thorchain/blob/main/chain/ethereum/contracts/THORChain_Router.sol#L280
  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
      if (!ethSuccess) {
        payable(address(msg.sender)).transfer(_safeAmount); // For failure, bounce back to vault & continue. // @audit should be returned without emitting the TransferOutAndCall event
      }
    }

    emit TransferOutAndCall(
      msg.sender,
      aggregator,
      _safeAmount,
      finalToken,
      to,
      amountOutMin,
      memo
    );
  }
```

## Tools Used
Manual review

## Recommended Mitigation Steps
It's recommended to add a return statement if ethSuccess is false to avoid emitting the event.


## Assessed type

Context
Refund functionality breaks protocol logic
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#L196
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L213
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L280
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L326


# Vulnerability details

## Impact
In cases where the recipient fails to receives native funds being transferred to him via any of the transfer out functions, the system does not have a way of knowing that the transfer have indeed failed. This causes the funds to stay in the vault and the recipient loses the funds.

## Proof of Concept
Whenever a vault carries out a transfer out transaction that sends native asset to an address and this operation fails the native asset is returned to the caller but the event is emitted like the transaction is successful.

Example: 

1. User calls [`depositWithExpiry()`](https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L131) on Ethereum to transfer funds.
2. The system picks up the deposit event and processes the tx.
3. The user must receive native funds on another chain but the native currency transfer that uses internal send call fails due to OOG error. Then the code sends the funds to the msg.sender which is the vault.

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


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
    );
  }
```
4. The TransferOut event is emitted which will indicate to the system that the transaction is completed but in fact the user never received the funds.
5. This results in a loss for the user.


## Tools Used
Manual review

## Recomme
Incorrect Information Displayed by Events in THORChain_Router.sol
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#L185-L207
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L209-L238
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L261-L293


# Vulnerability details

## Description
In the `THORChain_Router.sol` contract, various functions utilize events to display transaction details to users.
## Impact
There is a potential issue where the displayed information might be incorrect. Specifically, in the `transferOut()` , `_transferOutV5()` , and `transferOutAndCall()` functions, the contract includes logic to return funds to the sender if the initial transfer to the receiver fails.

1. If the call to transfer funds to the receiver fails due to any reason, the 
   receiver does not receive the intended amount of ETH.
2. The funds are then transferred back to the sender's account.
3. As a result, the actual transfer of funds to the receiver does not occur, and 
   the funds remain in the sender's account. However, the event emitted by the 
   contract incorrectly indicates that the complete amount has been transferred 
   to the receiver.
## POC
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L206
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L231-L237
https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/chain/ethereum/contracts/THORChain_Router.sol#L284-L292
### Test Case

Here is commands to run the POC:

```bash
forge test --mt test_EmitsWrongEvents -vvvv
```
Here is the proof of code:

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

import {THORChain_Router} from "../contracts/THORChain_Router.sol";
import {MockERC20} from "forge-std/mocks/MockERC20.sol";
import {ERC20Token} from "../contracts/Token.sol";
import {Test, console} from "forge-std/Test.sol";
contract BugTest is Test {
    THORChain_Router public router;
    MockERC20 public token;
    address public Alice = makeAddr("Alice");
    address public Vault = makeAddr("Vault");

    function setUp() public {
        token = new MockERC20();
        token.initialize("Test", "TEST", 18);
        router = new THORChain_Router();
    }
    function test_EmitsWrongEvents() public {
            ReceiverContract receiver = new ReceiverContract();
            vm.deal(Alice, 10 ether);
            vm.startPrank(Alice);
            router.transferOut{value: 10 ether}(payable(address(receiver)), address(0), 0, "swap");
            vm.stopPrank();
            // As the receiver contract does not receive the ether, but the 
           fu
Missing Event Handling for `TransferOutAndCallV5` in `smartcontract_log_parser`
Audit: 2024-06-thorchain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-thorchain/blob/e3fd3c75ff994dce50d6eb66eb290d467bd494f5/bifrost/pkg/chainclients/shared/evm/smartcontract_log_parser.go#L166


# Vulnerability details

The Go code for parsing events from the THORChain Router contract is missing handling for the `TransferOutAndCallV5` event. This event is crucial for processing V5 transactions correctly, and its absence could lead to initiating incorrect outbound transactions from vaults or other accounting issues in Thorchain.

## Impact
The impact of this missing event handling is that any transactions involving the `TransferOutAndCallV5` event will not be correctly processed or recorded by the parsing code. This could lead to incorrect balances, misattributed transactions, or other inconsistencies in the system including failure of initiating outbound transactions to the Layer-1 chain.

## Proof of Concept
The relevant code snippet from the provided Go code is missing handling for the `TransferOutAndCallV5` event. This event should be handled similarly to other events, with a dedicated parsing function that extracts relevant information such as the sender, receiver, amount, and other necessary data.

https://github.com/code-423n4/2024-06-thorchain/blob/main/bifrost/pkg/chainclients/shared/evm/smartcontract_log_parser.go

## Tools Used
Manual Review

## Recommended Mitigation Steps
To mitigate this vulnerability, the missing event handling for `TransferOutAndCallV5` should be implemented in the Go code. The missing event handling code should look something like this:

```go
type THORChainRouterTransferOutAndCallV5 struct {
    // Define the structure of the event fields here
}

func (scp *SmartContractLogParser) parseTransferOutAndCallV5(log etypes.Log) (*THORChainRouterTransferOutAndCallV5, error) {
    const TransferOutAndCallV5EventName = "TransferOutAndCallV5"
    event := new(THORChainRouterTransferOutAndCallV5)
    if err := scp.unpackVaultLog(event, TransferOutAndCallV5EventName, log); err != nil {
        return nil, err
    }
    return event, nil
}
```

This function should be added to the existing `SmartContractLogParser` struct along with other event parsing functions.


## Assessed type

Other
Wrong address will receive msg.value when call failed
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

eth is transferred to wrong address

## Proof of Concept

when aggregator fails to execute swapOutV5 when _transferOutAndCallV5 function is called

it should be recipient that receives the the msg.value

but now eth is sent to the aggregator


```solidity
bool sendSuccess = payable(aggregationPayload.target).send(msg.value);
```


## Tools Used

Nothing

## Recommended Mitigation Steps

```diff
+       bool sendSuccess = payable(aggregationPayload.recipient).send(msg.value); 
```





## Assessed type

Other