hats-finance / Fenix--0x9d7765a7ebd5b6322a30797a44a5428531970d3d

0 stars 1 forks source link

`SafeERC20.safeApprove` reverts for changing existing approvals #53

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): 0x451f09f8ecfacda777b913e57dcea753a9586c889c1417009eb818fcd2b7be49 Severity: medium

Description: Description\ The buybackTokenByV2 function interacts with general Blast tokens and ERC20 standard tokens. However, it does not reset the approval to zero before setting a new approval.This results in failed transactions or reverts and incompatibility with tokens.

The safeApprove function has explicit warning:

 // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'

Attack Scenario\

Initial State: The contract has an allowance of 100 tokens set for the DEX router.

Buyback Operation: The contract attempts to perform a buyback operation that requires an allowance of 200 tokens.

Approval Update: The contract directly sets the new allowance to 200 tokens without resetting the previous allowance to zero.

the safeApprove call will fail, causing the entire buyback operation to revert. Attachments

  1. Proof of Concept (PoC) File

    function buybackTokenByV2(
        address inputToken_,
        IRouterV2.route[] calldata inputRouters_,
        uint256 slippage_,
        uint256 deadline_
    ) external virtual override onlyCorrectInputToken(inputToken_) onlyCorrectSlippage(slippage_) returns (uint256 outputAmount) {
        _checkBuybackSwapPermissions();
    
        IERC20 inputTokenCache = IERC20(inputToken_);
    
        uint256 amountIn = inputTokenCache.balanceOf(address(this));
        if (amountIn == 0) {
            revert ZeroBalance();
        }
    
        address targetToken = _getBuybackTargetToken();
    
        IRouterV2PathProvider routerV2PathProviderCache = IRouterV2PathProvider(routerV2PathProvider);
    
        (IRouterV2.route[] memory optimalRoute, ) = routerV2PathProviderCache.getOptimalTokenToTokenRoute(
            inputToken_,
            targetToken,
            amountIn
        );
    
        uint256 amountOutQuote;
        if (optimalRoute.length > 0) {
            amountOutQuote = routerV2PathProviderCache.getAmountOutQuote(amountIn, optimalRoute);
        }
    
        if (inputRouters_.length > 1) {
            if (inputRouters_[0].from != inputToken_ || inputRouters_[inputRouters_.length - 1].to != targetToken) {
                revert InvalidInputRoutes();
            }
    
            if (!routerV2PathProviderCache.isValidInputRoutes(inputRouters_)) {
                revert InvalidInputRoutes();
            }
    
            uint256 amountOutQuoteInputRouters = routerV2PathProviderCache.getAmountOutQuote(amountIn, inputRouters_);
    
            if (amountOutQuoteInputRouters > amountOutQuote) {
                optimalRoute = inputRouters_;
                amountOutQuote = amountOutQuoteInputRouters;
            }
        }
    
        amountOutQuote = amountOutQuote - (amountOutQuote * slippage_) / SLIPPAGE_PRECISION;
        if (amountOutQuote == 0) {
            revert RouteNotFound();
        }
    
        IRouterV2 router = IRouterV2(routerV2PathProviderCache.router());
        inputTokenCache.safeApprove(address(router), amountIn);//@audit-not resetting to zero
    
        uint256 balanceBefore = IERC20(targetToken).balanceOf(address(this));
    
        uint256[] memory amountsOut = router.swapExactTokensForTokens(amountIn, amountOutQuote, optimalRoute, address(this), deadline_);
    
        uint256 amountOut = amountsOut[amountsOut.length - 1];
    
        assert(IERC20(targetToken).balanceOf(address(this)) - balanceBefore == amountOut);
        assert(amountOut > 0);
    
        emit BuybackTokenByV2(msg.sender, inputToken_, targetToken, optimalRoute, amountIn, amountOut);
    
        return amountOut;
    }
  2. Revised Code File (Optional)

    • the approval should be reset to zero before setting a new approval
inputTokenCache.safeApprove(address(router), 0); // Reset approval to zero first
inputTokenCache.safeApprove(address(router), amountIn); // Set new approval
0xmahdirostami commented 1 month ago

5