hats-finance / Thorn-protocol-0x1286ecdac50215a366458a14968fbca4bd95067d

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

users ROSE will be trapped in `StableSwapRouter` if the pool contract stops accepting ROSE #101

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

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

Github username: -- Twitter username: -- Submission hash (on-chain): 0x7c479257717652a3d2d199486505fede4caca754c6f8189f35e689f897585adb Severity: medium

Description: Description\ When a user attempts to swap ROSE tokens using the exactInputStableSwap function in the StableSwapRouter contract, there is a issue i.e where ROSE tokens will be trapped in the contract(and may be swapped by another user). This occurs if the pool contract does not accept ROSE or stops accepting ROSE

Attack Scenario\ lets understand with example

Attachments

  1. Proof of Concept (PoC) File

    Here, let's say a user calls exactInputStableSwap with srcToken set to ROSE.

    function exactInputStableSwap(
        address[] calldata path,
        uint256[] calldata flag,
        uint256 amountIn,
        uint256 amountOutMin,
        address to
    ) external payable nonReentrant returns (uint256 amountOut) {
        require(!isKill, "Contract is killed");
        address srcToken=  path[0];
        address dstToken = path[path.length - 1];
    
        // use amountIn == Constants.CONTRACT_BALANCE as a flag to swap the entire balance of the contract
    
        bool hasAlreadyPaid;
        if (amountIn == Constants.CONTRACT_BALANCE) {
            hasAlreadyPaid = true;
            if(srcToken==ROSE){
                amountIn = address(this).balance;
            }else{
                amountIn = IERC20(srcToken).balanceOf(address(this));
            }
        }
    
        if (!hasAlreadyPaid) {
            if(srcToken==ROSE){
                require(msg.value>=amountIn, "Invalid msg.value");
            }else{
                pay(srcToken, msg.sender, address(this), amountIn);
            }
        }
        _swap(path, flag);

    Now, this function calls the _swap() function internally


    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_);
                 IStableSwap(swapContract).exchange(k, j, amountIn_, 0);
            }
        }
    }

,and in the swap function, as we can see, it calls the exchange function at i.e., IStableSwap(swapContract).exchange{value: amountIn}(k, j, amountIn_, 0);.

 function exchange(
        uint256 i,
        uint256 j,
        uint256 dx,
        uint256 min_dy
    ) external payable nonReentrant {
        require(!is_killed, "Killed");
        if (!support_ROSE) {
            require(msg.value == 0, "Inconsistent quantity"); // Avoid sending ROSE by mistake.
        }

Now, the exchange() function will be invoked here. This function checks i.e., if the pool contract does not accept ROSE or it stops accepting ROSE, then the function will revert. The user's ROSE tokens will be trapped in the StableSwapRouter contract as the call to exchange function is reverted.

steps to reproduce