Solidity – Resolving ‘ERC20 Transfer Amount Exceeds Balance’ Error in Solidity Contract

erc-20soliditytransfertruffle

I'm encountering an error while trying to transfer tokens from one address to another in my Solidity contract. When I attempt to print the balance of msg.sender, it shows a value of 1000000000000000000000000, but when I try to execute the transfer function, it throws an error message stating: "VM Exception while processing transaction: revert ERC20: transfer amount exceeds balance."

Code Snippets:
Here's the relevant code from my contracts:

Token.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Token is ERC20, Ownable {
    constructor() ERC20("Token", "ANT") {
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }
}

Registry.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract Registry {
    Token public tokenContract;
    address public addr;
    mapping(address => address) public publicKeyToAccount;

    constructor(Token _tokenContract) {
        tokenContract = _tokenContract;
        addr = address(this);
    }

    function printBalance() external view returns (uint256) {
        return IERC20(tokenContract).balanceOf(msg.sender);
    }

    function transfer() external {
        IERC20(tokenContract).transfer(addr, 10);
    }
}

1_migrate.js

const Registry = artifacts.require("Registry");
const Token = artifacts.require("Token");

module.exports = async function (deployer) {
  await deployer.deploy(Token);
  const tokenContract = await Token.deployed();
  module.exports.tokenContract = tokenContract.address;
  await deployer.deploy(Registry, tokenContract.address);
};

Expected Behavior:
I expect the transfer function to execute successfully and transfer 10 tokens from msg.sender to the specified address (addr).

Actual Behavior:
The transfer function throws the error message "ERC20: transfer amount exceeds balance" even though the balance of msg.sender appears to be sufficient.

Steps Taken:

Deployed the Token contract and obtained its address.
Deployed the Registry contract, passing the Token contract address to its constructor.
Executed the printBalance function, which returned the expected balance of 1000000000000000000000000.
Attempted to execute the transfer function, which resulted in the error mentioned above.
Question:
What could be causing the "ERC20: transfer amount exceeds balance" error, even though the balance of msg.sender seems to be correct? Is there any issue with my contract code or the way I'm interacting with it? Any guidance or suggestions to resolve this issue would be greatly appreciated.

Best Answer

There is a problem with the way you are interacting with the contracts.

When the Token contract is deployed, it's constructor executes _mint which mints 1M tokens for the msg.sender which is the deployer address (EOA).

Registry contract would have a different address, which is not the deployer address.

When interacting with Registry contract using your deployer address, the call trace is deployer -> Registry, that's why in the Registry contract, the msg.sender is deployer address (since it is sending the call) and hence you are able to get the token balance for deployer.

However, when you want to transfer tokens, the call trace is deployer -> Registry -> Token. Hence when Token contract uses msg.sender it uses the Registry contract address.

To solve this: I suggest you not use msg.sender while trying this out initially and add a function argument for taking an address in the Token.constructor, Registry.printBalance to prevent confusion (you can later start using msg.sender). If you want to use Token.transfer, you need to mint the tokens on the Registry contract's address. If you want to you deployer address then you can use Token.transferFrom, however for that you need to Token.approve the registry from your deployer address.

Related Topic