Revert when using ERC20 transferFrom in a contract

I’ve a contract GetNewToken that receives OldToken and gives NewToken through the functions buyTokens

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

import './NewToken.sol';
import './OldToken.sol';
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract GetNewToken {

    string  public name = "Get New Token Contract";
    address public owner;
    NewToken public newToken;
    OldToken public oldToken;

    event Bought(address buyer, uint256 amount);
    event TransferFromFailed(uint256 amount);
    
    constructor(NewToken _newToken, OldToken _oldToken) public {
        newToken = _newToken;
        oldToken = _oldToken;
        owner = msg.sender;
    }

    function buyTokens(uint256 _amount) public {
        require(_amount > 0, "amount can not be 0");
        uint256 userBalance = oldToken.balanceOf(msg.sender);

        require(userBalance >= _amount, "Not enough Old Token in your account");
        bool success = oldToken.transferFrom(msg.sender, address(this), _amount);

        if(success) {
            uint256 newBalance = newToken.balanceOf(address(this));
            require(_amount <= newBalance, "Not enough New Tokens in the contract");
            newToken.transfer(msg.sender, _amount);
            emit Bought(msg.sender, _amount);
        } else {
            emit TransferFromFailed(_amount);
            revert("oldToken.transferFrom function failed");
        }

    }
}

OldTokens and NewTokens are standard ERC20 implementations. One of them is shown below (other is exactly similar with exception of name and symbol)

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

contract OldToken {
    string  public name = "Old Token";
    string  public symbol = "OLDT";
    uint256 public totalSupply = 1000000000000000000000000; // 1 million tokens
    uint8   public decimals = 18;

    event Transfer(
        address indexed _from,
        address indexed _to,
        uint256 _value
    );

    event Approval(
        address indexed _owner,
        address indexed _spender,
        uint256 _value
    );

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

    constructor() public {
        balanceOf[msg.sender] = totalSupply;
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value);
        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;
        emit Transfer(msg.sender, _to, _value);
        return true;
    }

    function approve(address _spender, uint256 _value) public returns (bool success) {
        allowance[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(_value <= balanceOf[_from]);
        require(_value <= allowance[_from][msg.sender]);
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        allowance[_from][msg.sender] -= _value;
        emit Transfer(_from, _to, _value);
        return true;
    }
}

The 2_deployed_token.js looks like below. It also transfers some NewToken to GetNewToken contract, and transfers some OldToken to accounts[1], which will then be used to call buyTokens function.

const NewToken = artifacts.require("NewToken");
const OldToken = artifacts.require("OldToken");
const GetNewToken = artifacts.require("GetNewToken");

module.exports = async function(deployer, network, accounts) {
    await deployer.deploy(NewToken);
    const newToken = await NewToken.deployed();

    await deployer.deploy(OldToken);
    const oldToken = await OldToken.deployed();

    await deployer.deploy(GetNewToken, newToken.address, oldToken.address);
    const getNewToken = await GetNewToken.deployed();

    await newToken.transfer(GetNewToken.address, "1000000000000000000000"); // 1000 tokens
    await oldToken.transfer(accounts[1], "1000000000000000000000"); // 1000 tokens
};

truffle-config.js looks like:

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 7545,
      network_id: "*"
    }
  },
  contracts_directory: './contracts/',
  contracts_build_directory: './abis/',
  compilers: {
    solc: {
      version: "0.6.2",
      optimizer: {
        enabled: true,
        runs: 200
      },
      evmVersion: "petersburg"
    }
  }
};

Now compile and migrate works fine (am using ganache as local blockchain):

truffle compile
truffle migrate --reset

The addresses are as follows (in ganache): OldToken 0xaA47e5555db895b230A16AF6860bf5DF442C7dB9

NewToken 0x43C68Efa858a9eA2F2DdD4e1AbDeA90a96f5C56E

GetNewToken 0x4b4a5d3Ad0ed472876e50FB0daa6ED2FA78488e2

Here are the steps then performed in truffle console:

NewToken.deployed().then(function(instance){newToken = instance})
OldToken.deployed().then(function(instance){oldToken = instance})

GetNewToken.deployed(newToken, oldToken).then(function(instance){return instance.buyTokens('1000000000000000000', {from:accounts[1]});});

which then returns the error:

Uncaught Error: Returned error: VM Exception while processing transaction: revert
    at evalmachine.<anonymous>:0:75
    at processTicksAndRejections (internal/process/task_queues.js:93:5) {
  hijackedStack: 'Error: Returned error: VM Exception while processing transaction: revert\n' +
    '    at Object.ErrorResponse (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3-core-helpers/src/errors.js:29:1)\n' +
    '    at /usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3-core-requestmanager/src/index.js:140:1\n' +
    '    at /usr/local/lib/node_modules/truffle/build/webpack:/packages/provider/wrapper.js:112:1\n' +
    '    at XMLHttpRequest.request.onreadystatechange (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3-providers-http/src/index.js:96:1)\n' +
    '    at XMLHttpRequestEventTarget.dispatchEvent (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request-event-target.js:34:1)\n' +
    '    at XMLHttpRequest._setReadyState (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:208:1)\n' +
    '    at XMLHttpRequest._onHttpResponseEnd (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:318:1)\n' +
    '    at IncomingMessage.<anonymous> (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:289:47)\n' +
    '    at IncomingMessage.emit (events.js:326:22)\n' +
    '    at IncomingMessage.EventEmitter.emit (domain.js:548:15)\n' +
    '    at endReadableNT (_stream_readable.js:1252:12)\n' +
    '    at processTicksAndRejections (internal/process/task_queues.js:80:21)'

Debugging the transaction hash using truffle debug 0xdf7af41dd709d19a75b497c25cde854328ea50176c35c6661416a316c382d9ad and pressing o highlights below line of the GetNewToken function buyTokens

debug(development:0xdf7af41d...)> o

Transaction halted with a RUNTIME ERROR.

There was no revert message.  This may be due to an in intentional halting expression, such as assert(), revert(), or require(), or could be due to an unintentional exception such as out-of-gas exceptions.
Please inspect your transaction parameters and contract code to determine the meaning of this error.

Stacktrace:
Error: Revert or exceptional halt
  at unknown function [address 0xaA47e5555db895b230A16AF6860bf5DF442C7dB9] (/Users/someuser/projects/project-folder/contracts/GetNewToken.sol:40:24)
  at GetNewToken.buyTokens [address 0x4b4a5d3Ad0ed472876e50FB0daa6ED2FA78488e2] (/Users/someuser/projects/project-folder/contracts/GetNewToken.sol:40:24)
  at GetNewToken [address 0x4b4a5d3Ad0ed472876e50FB0daa6ED2FA78488e2] (/Users/someuser/projects/project-folder/contracts/GetNewToken.sol:27:5)

Location of error:

GetNewToken.sol:
         
40:         bool success = oldToken.transferFrom(msg.sender, address(this), _amount);
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

debug(development:0xdf7af41d...)> o
Transaction has halted; cannot advance.

please ignore line numbers, as I had few comments in the GetNewToken.sol file

  1. Why I’m not able to use transferFrom? The accounts[1] already has OldTokens (as per migration).
  2. Also, when I use truffle console
truffle(development)> OldToken.deployed().then(function(instance){oldToken = instance})
truffle(development)> let a = oldToken.balanceOf(accounts[1])
undefined
truffle(development)> a
BN {
  negative: 0,
  words: [ 44040192, 40595831, 222044, <1 empty item> ],
  length: 3,
  red: null
}

why didn’t I get 1000000000000000000000 (as per migration script)?

Thanks!

1 Like

Hi @megatower66,

Welcome to the community :wave:

I didn’t see anywhere where the token holder approved an allowance to the GetNewToken contract, so assume that is the issue. GetNewToken can’t do transferFrom as it doesn’t have an allowance from the token holder.

I suggest looking at: Example on how to use ERC20 token in another contract

As an aside, I noticed that you were creating your own ERC20 token. I would recommend looking at inheriting from the OpenZeppelin ERC20 implementation: https://docs.openzeppelin.com/contracts/3.x/erc20#constructing-an-erc20-token-contract

1 Like

Hi @megatower66,

I wanted to check if you were able to resolve or if you still had issues?

1 Like

Got it @abcoathup! Thanks for the help, setting allowance resolved this!

1 Like