hats-finance / Thorn-protocol-0x1286ecdac50215a366458a14968fbca4bd95067d

GNU General Public License v3.0
0 stars 0 forks source link

The `_swap` is set to zero slippage by default and doesn't protect the users properly. #92

Open hats-bug-reporter[bot] opened 1 month ago

hats-bug-reporter[bot] commented 1 month ago

Github username: @catellaTech Twitter username: catellatech Submission hash (on-chain): 0xca61dcbf9bfe2303ceb41e6834f39e19dad96447ea165e96693e1683e906d4fd Severity: medium

Description:

Severity: Medium

Description:

The _swap function is designed to facilitate token exchanges through a swap contract. However, its current implementation contains several vulnerabilities that can be exploited, particularly concerning slippage logic and transaction timing.

Identified Issues

  1. Zero Slippage Setting:
    • Configuring slippage to zero can cause legitimate user transactions to fail if the price changes before their transaction is processed. Check the code:
    function _swap(address[] memory path, uint256[] memory flag) private {
        uint256 amountIn_;
        require(path.length - 1 == flag.length);
        for (uint256 i; i < flag.length; i++) {
            (address input, address output) = (path[i], path[i + 1]);
            (uint256 k, uint256 j, address swapContract) =
                SmartRouterHelper.getStableInfo(stableSwapFactory, input, output, flag[i]);
            if (input == ROSE) {
                amountIn_ = address(this).balance;
                IStableSwap(swapContract).exchange{ value: amountIn_ }(k, j, amountIn_, 0);
            }
            if (input != ROSE) {
                amountIn_ = IERC20(input).balanceOf(address(this));
                TransferHelper.safeApprove(input, swapContract, amountIn_); 
                // This means that anyone with enough capital can force arbitrarily large slippage by
                // sandwiching
                // transactions, close to 100%.

                IStableSwap(swapContract).exchange(k, j, amountIn_, 0); // @audit <-- slippage set to 0
            }
  1. Frontrunning Vulnerability:
    • An attacker can frontrun a legitimate user's transaction, altering the expected amountOut and causing the transaction to fail due to the check require(amountOut >= amountOutMin).

Impact:

Proposed Improvements

To mitigate these vulnerabilities, the following improvements are recommended for the _swap function:

Improved Code for the _swap Function

Below is an enhanced version of the _swap function that incorporates these suggestions:

function _swap(address[] memory path, uint256[] memory flag, uint256 amountOutMin) private {
    uint256 amountIn_;
    require(path.length - 1 == flag.length, "Invalid path or flag length");

    for (uint256 i; i < flag.length; i++) {
        (address input, address output) = (path[i], path[i + 1]);
        (uint256 k, uint256 j, address swapContract) = SmartRouterHelper
            .getStableInfo(stableSwapFactory, input, output, flag[i]);

        if (input == ROSE) {
            amountIn_ = address(this).balance;
            IStableSwap(swapContract).exchange{value: amountIn_}(k, j, amountIn_, 0);
        } else {
            amountIn_ = IERC20(input).balanceOf(address(this));
            TransferHelper.safeApprove(input, swapContract, amountIn_);

            // Estimate the expected amount out before the swap
            uint256 estimatedAmountOut = IStableSwap(swapContract).getEstimatedAmountOut(k, j, amountIn_);

            // Execute the exchange 
            IStableSwap(swapContract).exchange(k, j, amountIn_, amountOutMin); // <- proper slippage setUp
        }
    }
}

Improvements in the exactInputStableSwap Function

Additionally, here is the adjusted exactInputStableSwap function to include slippage:

function exactInputStableSwap(
    address[] calldata path,
    uint256[] calldata flag,
    uint256 amountIn,
    uint256 amountOutMin,  // Minimum output tokens expected
    address to
) external payable nonReentrant returns (uint256 amountOut) {

    require(!isKill, "Contract is killed");

    // Perform the swap
    _swap(path, flag, amountOutMin);  // Pass the slippage to _swap

    // Obtain the output amount
    if (dstToken == ROSE) {
        amountOut = address(this).balance;
    } else {
        amountOut = IERC20(dstToken).balanceOf(address(this));
    }

    // Ensure the output amount is greater than or equal to the expected minimum
    require(amountOut >= amountOutMin, "Output amount too low");  // This line may cause the user's initial tx to fail if slippage is too high.

    // Adjust the destination address and execute the transfer
    if (to == Constants.MSG_SENDER) {
        to = msg.sender;
    } else if (to == Constants.ADDRESS_THIS) {
        to = address(this);
    }

    if (to != address(this)) {
        pay(dstToken, address(this), to, amountOut);
    }

    emit StableExchange(msg.sender, amountIn, path[0], amountOut, path[path.length - 1], to);
}