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?