[Ethereum] How to transferFrom an external erc20 token with IERC20

contract-developmenterc-20solidity

Currently, I try to send Wrapped ETH from my account to a contract address by using IERC20. But it returned an error on Remix. By the way, I could call token.balanceOf(msg.sender) but can't execute token.transferFrom(msg.sender, address(this), amount) somehow.

My code is below

pragma solidity ^0.6.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol";


contract Test2 {
       
        IERC20 public token;
       
        constructor(address _token) public {
        token = IERC20(_token);
        }
        
        function getBalance() public view returns(uint) {
            uint balance_ = token.balanceOf(msg.sender);
            return balance_;
        }
        
        function deposit(uint amount) public {
            require(amount <= token.balanceOf(msg.sender), "you don't have enough balance.");
            token.transferFrom(msg.sender, address(this), amount);
        }
    
}

And ERC20 token I want to send is Wrapped ETH. So I copied the WETH contract and deployed on Remix before I deployed this contract. And when I deploy the above contract I add deployed
WETH contract address as an argument of the constructor.

The error message is always like below on Javascript VM on Remix IDE. But this error message doesn't tell what the real problem is. making my contract payable doesn't make sense and I checked that the balance I own is always more than I want to send.

VM error: revert. revert The transaction has been reverted to the initial state. Note: The called function should be payable if you send value and the value you send should be less than your current balance.

WETH contract is here but actually which erc20 I use doesn't matter because I tried the same thing with other deployed ERC20 tokens and even external LINK contract on the Ropsten net (https://ropsten.etherscan.io/address/0x20fe562d797a42dcb3399062ae9546cd06f63280)


contract WETH9 {
    string public name     = "Wrapped Ether";
    string public symbol   = "WETH";
    uint8  public decimals = 18;

    event  Approval(address indexed src, address indexed guy, uint wad);
    event  Transfer(address indexed src, address indexed dst, uint wad);
    event  Deposit(address indexed dst, uint wad);
    event  Withdrawal(address indexed src, uint wad);

    mapping (address => uint)                       public  balanceOf;
    mapping (address => mapping (address => uint))  public  allowance;

    function() public payable {
        deposit();
    }
    function deposit() public payable {
        balanceOf[msg.sender] += msg.value;
        Deposit(msg.sender, msg.value);
    }
    function withdraw(uint wad) public {
        require(balanceOf[msg.sender] >= wad);
        balanceOf[msg.sender] -= wad;
        msg.sender.transfer(wad);
        Withdrawal(msg.sender, wad);
    }

    function totalSupply() public view returns (uint) {
        return this.balance;
    }

    function approve(address guy, uint wad) public returns (bool) {
        allowance[msg.sender][guy] = wad;
        Approval(msg.sender, guy, wad);
        return true;
    }

    function transfer(address dst, uint wad) public returns (bool) {
        return transferFrom(msg.sender, dst, wad);
    }

    function transferFrom(address src, address dst, uint wad)
        public
        returns (bool)
    {
        require(balanceOf[src] >= wad);

        if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
            require(allowance[src][msg.sender] >= wad);
            allowance[src][msg.sender] -= wad;
        }

        balanceOf[src] -= wad;
        balanceOf[dst] += wad;

        Transfer(src, dst, wad);

        return true;
    }
}

Please help me if you know what the problem is! thanks!

Best Answer

Let's take a look at this method:

token.transferFrom(msg.sender, address(this), amount);

What the contract does is transfer a quantity amount of WETH token from the caller of the function (msg.sender) to the contract (address(this)).

Now let's see the transferFrom method. The causes of transaction failure are as follows :

        if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
        require(allowance[src][msg.sender] >= wad);
        allowance[src][msg.sender] -= wad;
    }

In your Test2 contract, the caller of deposit is the user who interact with the contract. However, the caller of transferFrom is the contract itself, not the user. Then msg.sender is not the same in Test2 and WETH9 contracts during the transaction execution. The user must therefore approve the Test2 contract to allow it to transfer their token.

See this for more information : Approve contract to withdraw funds from users's wallet

Related Topic