1

I'm trying to create a token (calling it MyToken in this question) that on each transfer, it takes a fee from the transfer amount and does the following:

  • calling the uniswap router to exchange this amount of MyToken for ETH
  • send this amount of ETH to a known address

I defined the following function to do the exchange and send to this wallet:

function sendEthToWallet(uint256 amountToken) private { 
        address[] memory path = new address[](2);   
        path[0] = address(this);    
        path[1] = _uniswapV2Router.WETH();  
        _approve(address(this), address(uniswapV2Router), amountToken); 
        // make the swap    
        _uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            amountToken,    
            0, // accept any amount of ETH  
            path,   
            address(this),  
            block.timestamp 
        );  
      _knownWallet.transfer(address(this).balance);
    }

If I call this function only, the swap of the tokens and the sending works fine.

However, if I call this inside a transfer, it breaks giving me this error:

Internal JSON-RPC error. { "code": 3, "message": "execution reverted: TransferHelper::transferFrom: transferFrom failed"}

My Transfer function:

function _transfer(address from, address to, uint256 amount) private {  
    if (from != _uniswapV2Router && from != _pairAddress) {
       taxToTake = amount.mul(_fee).div(10**2)
       amountToSend = amount.sub(taxToTake)
       balanceOf[from] = balanceOf[from].sub(amountToSend); 
       balanceOf[to] = balanceOf[to].sub(amountToSend); 
       sendEthToWallet(taxToTake)
       emit Transfer(from, to, amountToSend);
    }else{
       balanceOf[from] = balanceOf[from].sub(amount);   
       balanceOf[to] = balanceOf[to].sub(amount);   
       emit Transfer(from, to, amount);
    }
}

In the transfer function, I have this if, in a way that if the transfer is called by uniswap router or the pair address, it doesn't tries to take the fee again.

The problem seems to be regarding some problem in trasferFrom inside sendEthToWallet when I call the uniswap router sendExactTokensForEth.

Is it right to approve the uniswap router to approve the address of the contract? Which is the msg.sender of that function? Since it's a transaction inside the contract, I would suppose the msg.sender it's the contract address.

What I cannot understand is why if I call the sendEthToWallet function alone it works, while if I make a transfer, it doesn't work.

Any idea?

1 Answer 1

0

Problem #1

Your transfer function is broken. It subtracts the amount from both the sender and receiver.

                                  v
balanceOf[from] = balanceOf[from].sub(amountToSend); 
balanceOf[to] = balanceOf[to].sub(amountToSend); 
                              ^

Thus, sending to an address that does not hold any tokens will result in an underflow (which the .sub function of SafeMath will catch and revert execution)

You can fix this problem by changing the code to balanceOf[to] = balanceOf[to].add(amountToSend);

Problem #2

This code will break if you try to sell the tokens. In a sell, you don't sync the pair after changing it's balance, and then attempt to perform another swap on an out-of-sync pair, which will be rejected and revert the entire transaction.

How the router and pair work together to perform trades

Upon a sell, the router is essentially going to transferFrom tokens to the pair from your address. Your fixed code will change the balances as so:

 balanceOf[you] = balanceOf[you].sub(amountToSend); 
 balanceOf[pair] = balanceOf[pair].add(amountToSend);

So, the pair balance is now changed. The pair determines the price of the token based on its reserves (balance) of the token itself and its reserves of ETH (WETH). The pair requires a re-sync every time either of its reserves changes to update the new price. Until a re-sync happens, all new trades will fail. This re-sync can happen as either:

  • an explicit sync() call, which is a pair function that syncs the price to match its new reserves, but doesn't change those reserves.
  • a swap(...) call, which is another pair function that first determines which of its reserves increased, ie. if a user sent ETH (buy tx) or sent tokens (sell tx) to the pair. If the user sent ETH, it sends back proportional tokens and re-syncs. If the user sent tokens, it sends back proportional ETH and re-syncs. This is the function we call when performing trades.

This is one of the reasons why we use the router instead of swapping with the pair directly, because we really require two transactions, eg. for a sell:

  1. transfer tokens to pair
  2. call swap(...) on pair to get ETH (WETH) in exchange.

The router does exactly this for us in a single function, allowing us to treat these two transactions as if they were one:

function swapExactTokensForETH(...) {
    <code to calculate amount and path> // omitted for simplicity

    TransferHelper.safeTransferFrom(path[0], msg.sender, pair, amounts[0]);
    _swap(amounts, path, address(this));

    <convert WETH to ETH and send back to user> // also omitted
}

As you can see, the TransferHelper.safeTransferFrom(...) call is what calls your transfer functions, which change the balances and put the pair out of sync. Immediately after, the next line _swap(...) re-syncs the pair and sends the output asset back to the user, completing the "trade".

Your code tries to sell on an out-of-sync pair

Remember that during a sell, our transfer function is called by the router, with a _swap() coming up as soon as our code is done executing. Therefore, as soon as we change balanceOf[pair], the pair will be out-of-sync and unable to perform new swaps until execution is returned to the router to perform its re-sync via _swap().

However, your following code attempts to perform another sell immediately after changing the pair balance (and putting it out of sync) and before the router gets to re-sync it:

/* during your sell: */
balanceOf[you] = balanceOf[you].sub(amountToSend); 
balanceOf[pair] = balanceOf[pair].add(amountToSend); 
// starting from this line, the pair is out of sync until router calls _swap()

sendEthToWallet(taxToTake)    // attempts to perform a new sell on out of sync pair

Therefore, you are trying to sell on an out of sync pair and execution will be reverted and the tx will fail.

You can fix this by moving the sendEthToWallet(taxToTake); call before the two lines that change the balances. Then sendEthToWallet() will be performing a sell on a still-in-sync pair with no problem. Since sendEthToWallet() uses one of the router's swapTokensForEth() functions, the pair will already be synced back by the time sendEthToWallet() finishes, and the code execution will proceed without issue.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.