swapExactTokensForETHSupportingFeeOnTransferTokens and _approve

I am trying to implement a simple swap (my ERC20 token against ETH) and noticed a strange bug. The _approve function does not seem to work properly and is causing Uniswap to reject any buy/sell attempt.

Here is the relevant code:

    constructor(
        string memory name_,
        string memory symbol_,
        uint8 decimals_,
        uint256 totalSupply_,
        address router_,
        uint8 liquidityTax_
    ) ERC20(name_, symbol_) {
        _decimals = decimals_;
        _liquidityTax = liquidityTax_;
        _minTokensRequiredBeforeSwap = 1 * 10**_decimals; // To change on prod
        // _minTokensRequiredBeforeSwap = 10**6 * 10**_decimals;
        _uniswapV2Router = IUniswapV2Router02(router_);

        address pair =
            IUniswapV2Factory(_uniswapV2Router.factory()).getPair(
                _uniswapV2Router.WETH(),
                address(this)
            );

        // Pair not yet created
        if (pair == address(0)) {
            _uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory())
                .createPair(_uniswapV2Router.WETH(), address(this));
        } else {
            _uniswapV2Pair = pair;
        }

        setIsExcludedFromPayingFees(address(this), true);
        setIsExcludedFromPayingFees(owner(), true);

        _mint(owner(), totalSupply_ * (10**decimals_));
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal override notNull(amount) {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        require(
            sender != recipient,
            "_transfer: 'sender' cannot also be 'recipient'"
        );

        if (!_isExcludedFromPayingFees[sender]) {
            amount = _beforeTokenTransfer(sender, amount);
        }

        uint256 senderBalance = _balances[sender];
        require(
            senderBalance >= amount,
            "ERC20: transfer amount exceeds balance"
        );
        _balances[sender] = senderBalance - amount;
        _balances[recipient] += amount;

        emit Transfer(sender, recipient, amount);
    }

    // Track: https://forum.openzeppelin.com/t/updating-balance-on-inherited-contracts/8133
    function _beforeTokenTransfer(address sender, uint256 amount)
        internal
        returns (uint256)
    {
        uint256 updatedAmount = amount;

        if (_liquidityTax != 0) {
            uint256 liquidityFee = (amount * _liquidityTax) / (10**2);

            _balances[sender] -= liquidityFee;
            _balances[address(this)] += liquidityFee;

            bool overMinTokensRequiredBeforeSwap =
                _balances[address(this)] >= _minTokensRequiredBeforeSwap;

            if (
                overMinTokensRequiredBeforeSwap &&
                !_inSwapAndLiquify &&
                _isAutoSwapAndLiquify
                // && sender != _uniswapV2Pair // not sure why this is needed... 🤔
            ) {
                uint256 halfContractBalance = _balances[address(this)] / 2;
                uint256 ethReceived = _swap(halfContractBalance);
                // (uint256 amountETH, uint256 amountToken) =
                //     _liquify(halfContractBalance, ethReceived);

                // _totalLiquidityETH += amountETH;
                // _totalLiquidity += amountToken;
            }

            updatedAmount -= liquidityFee;
        }

        return updatedAmount;
    }

    // Required to recieve ETH from uniswapV2Router on swaps
    receive() external payable {}

    function _swap(uint256 amountIn) private returns (uint256) {
        // contract's current BNB/ETH balance
        uint256 initialBalance = address(this).balance;

        // swap half of the tokens to ETH
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = _uniswapV2Router.WETH();

        _approve(address(this), address(_uniswapV2Router), amountIn);

        // Swap tokens for ETH/BNB
        _uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            amountIn,
            0, // TODO: use an Oracle
            path,
            address(this), // this contract will receive the ETH that were swapped from the token
            block.timestamp + 60 * 1000
        );

        // Figure out the exact amount of tokens received from swapping
        // Check: https://github.com/Uniswap/uniswap-v2-periphery/issues/92
        uint256 ethReceived = address(this).balance - initialBalance;

        // TODO: Check if a similar event is already emitted
        emit SwapExactTokensForETHSupportingFeeOnTransferTokens(
            amountIn,
            ethReceived
        );

        return ethReceived;
    }

I came to the conclusion that the error is caused by the _allowance method because when trying to troubleshot this bug I ran the following command that shows that either the code fails before the swapExactTokensForETHSupportingFeeOnTransferTokens being called or that it is the cause of this failure (later option is most likely given the error returned by Uniswap).

truffle(ropsten)> await token.allowance(token.address,"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D").then(x => console.log(x.toString()))
0

Further information available at: https://github.com/Uniswap/uniswap-v2-core/issues/119

If someone could help with this that would be fabulous already spent 2 full days on this bug with no success at all! Starting to give up… Although this - Uniswap - is (in the paper) a very straightforward lib/function to use.

1 Like

You must approve the router address by your own

Can you please explain in more details?

Do you have an Approve function on your token contract?

Well yeah as you can see on the code above, I am extending openzepplin and calling _approve.

You are calling allowances

1 Like

Oh I am calling allowance on truffle to check if the approve worked (and it seems it didn’t), all the relevant code is on the first post.

@aress31 did you manage to find a solution? I’m having the same problem trying to trade tokens like safemoon with the swapExactTokensForETHSupportingFeeOnTransferTokens putting the exact data that is present in my transaction from pancake swap to match it and it still fails…

@aress31 @Jesus_Christ did either of you figure this out?

I tried downgrading to solidity 0.6.2 but no luck.

I also tried making sure there was a lot of volume in the liquidity pool but still didn't work.

Would greatly appreciate if you could share your findings!