IERC20 token.transfer always fails when called from within smart contract

36 Views Asked by At

I created an ERC20 token that adhered to the IERC20 interface and deployed it to a testnet using Remix. I'm able to call the token functions directly to transfer tokens between addresses without any issues. However, I'm running into a strange problem when I try to execute the same token.transfer functions from within another contract.

Here's a pretty minimal testcase. I launch the testCase contract passing in the address of my already deployed ERC20 token to the constructor.

I have two different versions of the function attempting to do the same thing

 // SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.2 <0.9.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract TestCase
{
    IERC20 token;
    bool tokenIsSmartContract;

    constructor( address tokenAddress )
    {
        tokenIsSmartContract = tokenAddress.code.length > 0;
        token = IERC20(tokenAddress);
    }

    error InsufficientTokens();
    error TokenPaymentFailed();
    

    function transfer_from_caller_to_contract_v1() external
    {
        uint sendAmount = 1;

        uint balance = token.balanceOf(msg.sender);

        //verify they have enough balance
        if( balance < sendAmount )
            revert InsufficientTokens();

        //attempt to send 1 token from msg.sender (ie, the caller of foo()) to this contract
        address payable to = payable(address(this));
        if( !token.transfer(to, sendAmount) ) revert TokenPaymentFailed();
   }

    error FailedToApprove();
    error InsufficientAllowance();

   function transfer_from_caller_to_contract_v2() external
    {
        uint sendAmount = 1;

        uint balance = token.balanceOf(msg.sender);

        //verify they have enough balance
        if( balance < sendAmount )
            revert InsufficientTokens();

        //I want to send 1 token from the caller of foo() to this contract
        
        address payable to = payable(address(this)); //the address that will receive funds
        address spender = address(this); //I think spender would be this contract
        address owner = msg.sender; //the holder of the tokens
        address from = owner; //the owner is the person is the one we want to transfer from

        //Sets a value amount of tokens as the allowance of spender over the caller's tokens.
        if( !token.approve(spender, sendAmount) )
            revert FailedToApprove();

        //Returns the remaining number of tokens that spender will be allowed to spend 
        // on behalf of owner through {transferFrom}. This is zero by default.
        uint allowed = token.allowance(owner, spender);
        if( allowed < sendAmount ) revert InsufficientAllowance();
        
        //Moves a value amount of tokens from from to to using the allowance mechanism. 
        // the value is then deducted from the caller's allowance.
        if( !token.transferFrom(from, to, sendAmount) ) revert TokenPaymentFailed();
   }


}
1

There are 1 best solutions below

0
Zayd On

If your goal is to transfer tokens from the user’s address (who is calling the testCase.foo() function) to the testCase contract (or another address), you need to use the transferFrom method instead of transfer. The transferFrom method allows a contract to transfer tokens on behalf of an address, given that the address has previously allowed the contract to do so up to a certain amount by calling the approve method on the token contract.

function foo(uint sendAmount) external {
// Ensure the contract is allowed to spend at least `sendAmount` tokens on behalf of `msg.sender`
uint256 allowance = token.allowance(msg.sender, address(this));
if(allowance < sendAmount) revert InsufficientTokens();

// Transfer `sendAmount` tokens from `msg.sender` to this contract
if (!token.transferFrom(msg.sender, address(this), sendAmount)) revert TokenPaymentFailed();

}