TransferHelper::transferFrom: transferFrom failed from UniswapV2Call flash swap contract

Hello, after days of failures and countless searches, i ask for help to understand why my flash swap contract returns the "TransferHelper::transferFrom: transferFrom failed" error, the error occurs in uniswapV2Call -> swapExactTokensForTokens -> TransferHelper.safeTransferFrom when i try to sell the borrowed tokens from Uniswap to Sushiswap. This is the contract:

// SPDX-License-Identifier: MIT
pragma solidity =0.6.6;

import "@uniswap/v2-periphery/contracts/libraries/UniswapV2Library.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol"; 
import "@uniswap/v2-core/contracts/interfaces/IERC20.sol";
import "@uniswap/lib/contracts/libraries/TransferHelper.sol";

// flash swap contract
contract FlashSwapTest1 is IUniswapV2Callee {

    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    using SafeMath for uint256;

    address public constant UniswapV2Factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
    address public constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    IUniswapV2Router02 public uni_router = IUniswapV2Router02(UNISWAP_V2_ROUTER);

    address public constant SushiswapV2Factory = 0xc35DADB65012eC5796536bD9864eD8773aBc74C4;
    address public constant SUSHISWAP_V2_ROUTER = 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506;
    IUniswapV2Router02 public sushi_router = IUniswapV2Router02(SUSHISWAP_V2_ROUTER);

    modifier ensure(uint deadline) {
        require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
        _;
    }

    // calculates the CREATE2 address for a Sushiswap pair without making any external calls
    function pairForSushiswap(address factory, address tokenA, address tokenB) public pure returns (address pair) {
        (address token0, address token1) = UniswapV2Library.sortTokens(tokenA, tokenB);
        pair = address(uint(keccak256(abi.encodePacked(
                hex'ff',
                factory,
                keccak256(abi.encodePacked(token0, token1)),
                hex'e18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303' // init code hash
            ))));
    }

    // **** SWAP ****
    // requires the initial amount to have already been sent to the first pair
    // note: _swap original function from UniswapV2Router02.sol
    function _sushi_swap(uint[] memory amounts, address[] memory path, address _to) public {
        for (uint i; i < path.length - 1; i++) {
            (address input, address output) = (path[i], path[i + 1]);
            (address token0,) = UniswapV2Library.sortTokens(input, output);
            uint amountOut = amounts[i + 1];
            (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
            address to = i < path.length - 2 ? pairForSushiswap(SushiswapV2Factory, output, path[i + 2]) : _to;
            IUniswapV2Pair(pairForSushiswap(SushiswapV2Factory, input, output)).swap(
                amount0Out, amount1Out, to, new bytes(0)
            );
        }
    }

    // fetches and sorts the reserves for a pair
    function _getReserves(address factory, address tokenA, address tokenB) public view returns (uint reserveA, uint reserveB) {
        (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
        (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairForSushiswap(factory, tokenA, tokenB)).getReserves();
        (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
    }

    // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
    function _getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) {
        require(amountIn > 0, "My_UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT");
        require(reserveIn > 0 && reserveOut > 0, "My_UniswapV2Library: INSUFFICIENT_LIQUIDITY");
        uint amountInWithFee = amountIn.mul(997);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);
        amountOut = numerator / denominator;
    }

    // performs chained getAmountOut calculations on any number of pairs
    function _getAmountsOut(address factory, uint amountIn, address[] memory path) public view returns (uint[] memory amounts) {
        require(path.length >= 2, "My_UniswapV2Library: INVALID_PATH");
        amounts = new uint[](path.length);
        amounts[0] = amountIn;
        for (uint i; i < path.length - 1; i++) {
            (uint reserveIn, uint reserveOut) = _getReserves(factory, path[i], path[i + 1]);
            amounts[i + 1] = _getAmountOut(amounts[i], reserveIn, reserveOut);
        }
    }

    //Swaps an exact amount of input tokens for as many output tokens as possible on SUSHISWAP
    function _swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] memory path,
        address to, //Recipient of the output tokens.
        uint deadline
    ) public ensure(deadline) returns (uint[] memory amounts) {
        amounts = _getAmountsOut(SushiswapV2Factory, amountIn, path);
        require(amounts[amounts.length - 1] >= amountOutMin, "My_UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT");
        TransferHelper.safeTransferFrom(
            path[0], // token
            msg.sender, // from 
            pairForSushiswap(SushiswapV2Factory, path[0], path[1]), // to
            amounts[0] // value
        );
        _sushi_swap(amounts, path, to);
    }

    function executeTrade(address token0, address token1, uint amount0, uint amount1) public {

        // get liquidity pair address for tokens on uniswap
        address pairAddress = IUniswapV2Factory(UniswapV2Factory).getPair(token0, token1); 

        // make sure the pair exists in uniswap 
        require(pairAddress != address(0), "Could not find pool on uniswap"); 

        bytes memory data = abi.encode(pairAddress);

        IUniswapV2Pair(pairAddress).swap(amount0, amount1, address(this), data);
    }

    function uniswapV2Call(address _sender, uint _amount0, uint _amount1, bytes calldata _data) override external {

        // the path is the array of addresses to capture pricing information 
        address[] memory pathA = new address[](2);
        address[] memory pathB = new address[](2);
        
        // get the amount of tokens that were borrowed in the flash loan amount 0 or amount 1 
        // call it amountTokenBorrowed and will use later in the function 
        uint amountTokenBorrowed = _amount0 == 0 ? _amount1 : _amount0; 

        // get the addresses of the two tokens from the uniswap liquidity pool 
        address token0 = IUniswapV2Pair(msg.sender).token0(); 
        address token1 = IUniswapV2Pair(msg.sender).token1(); 

        // make sure the call to this function originated from
        // one of the pair contracts in uniswap to prevent unauthorized behavior
        require(msg.sender == UniswapV2Library.pairFor(UniswapV2Factory, token0, token1), "Invalid Request");
        // check sender holds the address who initiated the flash loans
        require(_sender == address(this), "!sender");

        // make sure one of the amounts = 0 
        require(_amount0 == 0 || _amount1 == 0);

        // create and populate path array for sushiswap _swapExactTokensForTokens function.  
        pathA[0] = _amount0 == 0 ? token1 : token0;
        pathA[1] = _amount0 == 0 ? token0 : token1;
        // create and populate path array for uniswap getAmountsIn function.
        pathB[0] = _amount0 == 0 ? token0 : token1;
        pathB[1] = _amount0 == 0 ? token1 : token0;

        //get Sushiswap pair address
        address sushi_pair = pairForSushiswap(SushiswapV2Factory, token0, token1);

        // create a pointer to the token we are going to sell on sushiswap 
        IERC20 token = IERC20(_amount0 == 0 ? token1 : token0);
        IERC20 tokenUNI = IERC20(msg.sender);
        IERC20 tokenSUSHI = IERC20(sushi_pair); 
        IERC20 outputToken = IERC20(_amount0 == 0 ? token0 : token1);
        
        // approve the sushiSwapRouter to spend our tokens so the trade can occur            
        token.approve(SUSHISWAP_V2_ROUTER, amountTokenBorrowed);
        tokenUNI.approve(SUSHISWAP_V2_ROUTER, amountTokenBorrowed);
        token.approve(UNISWAP_V2_ROUTER, amountTokenBorrowed);
        tokenUNI.approve(UNISWAP_V2_ROUTER, amountTokenBorrowed);
        outputToken.approve(SUSHISWAP_V2_ROUTER, amountTokenBorrowed);
        tokenSUSHI.approve(SUSHISWAP_V2_ROUTER, amountTokenBorrowed);
        token.approve(address(this), amountTokenBorrowed);
        tokenUNI.approve(address(this), amountTokenBorrowed);
        outputToken.approve(address(this), amountTokenBorrowed);
        tokenSUSHI.approve(address(this), amountTokenBorrowed);
        token.approve(msg.sender, amountTokenBorrowed);
        tokenUNI.approve(msg.sender, amountTokenBorrowed);
        outputToken.approve(msg.sender, amountTokenBorrowed);
        tokenSUSHI.approve(msg.sender, amountTokenBorrowed);

        // calculate the amount of tokens we need to reimburse uniswap for the flashloan 
        uint amountRequired = UniswapV2Library.getAmountsIn(UniswapV2Factory, amountTokenBorrowed, pathB)[0]; 
        
        // sell the token we borrowed from uniswap on sushiswap 
        uint amountReceived = _swapExactTokensForTokens(amountTokenBorrowed, amountRequired, pathA, address(this), block.timestamp + 60)[1]; 

        // fail if we didn't get enough tokens
        require(amountReceived > amountRequired, "amountReceived <= amountRequired!");
        
        // amount to payback flashloan 
        outputToken.transfer(msg.sender, amountRequired);   

        // send profit (remaining tokens) back to the address that initiated the transaction 
        outputToken.transfer(owner, amountReceived - amountRequired);  
    }
    
}

I checked with Tenderly the input values to swapExactTokensForTokens and they are correct:

{
  "amountIn": "100000000000000000000",
  "amountOutMin": "89506386512945284445",
  "path": [
    "0xTokenA_borrowed", //just hiding the real addr
    "0xTokenB_to_receive" //just hiding the real addr
  ],
  "to": "0xFlashSwap", //just hiding the real addr
  "deadline": "1676458833"
}

While getAmountsOut(inside swapExactTokensForTokens) returns 93238998313501573286 which is enough to cover the amountRequired.

I also gave several approvals before to swapExactTokensForTokens as you can see in the contract...

These are the TransferHelper.safeTransferFrom(inside swapExactTokensForTokens) input values:

{
  "token": "0xTokenA_borrowed",
  "from": "Uniswap Pair", //just hiding the real addr
  "to": "Sushiswap Pair", //just hiding the real addr
  "value": "100000000000000000000"
}

Than output is "execution reverted".

What could be the problem?

You should probably use address(this) here.


Other than that, I see in your contract function a bunch of calls to approve, in which this contract function approves the contract itself:

token.approve(address(this), amountTokenBorrowed);
tokenUNI.approve(address(this), amountTokenBorrowed);
outputToken.approve(address(this), amountTokenBorrowed);
tokenSUSHI.approve(address(this), amountTokenBorrowed);

This is pretty useless, because an address doesn't need an approval to transfer ERC20 from itself.

The bigger picture here, when taking both of my comments into account, is that you probably don't fully understand how the 'approve/transferFrom' mechanism works.

The problem actually occurs in any swapTokensForTokens() method on any univ2 based router.

Why does this happen?

Look closely at the safeTransfer function inside any of these methods. You’ll see it checks for a msg.sender

This is a problem because the flash swap is actually a callback function. Meaning that according to the router, msg.sender is the univ2 pair!