Unit testing : ERC20: transfer amount exceeds balance

Hi, I'm new into openzeppelin and into Token creation.

I just created a token implementing openzeppelin ERC20 class and I could deploy it into Kovan test blockchain and make transfer between two Metamask accounts.

I'm currently trying to implement automated tests using openzeppelin test environment.

I'm currently able to create my token and to mint and burn tokens on accounts, however I can't transfer between two accounts as I'm having the following error:

Error: Returned error: VM Exception while processing transaction: revert ERC20: transfer amount exceeds balance -- Reason given: ERC20: transfer amount exceeds balance.

I also tried using transferFrom function instead of transfer but I'm having this error:

Error: Returned error: VM Exception while processing transaction: revert ERC20: insufficient allowance -- Reason given: ERC20: insufficient allowance.

:1234: Code to reproduce

Here is the token implementation

//TestCoin.sol

pragma solidity ^0.8.0;

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

contract TestCoin is ERC20 {

    uint256 private _initialSupply;

    constructor() ERC20("TestCoin", "TSC") {
        _initialSupply = 10000000000000000000000;
        _mint(msg.sender, _initialSupply);
    }

    function mint(address account, uint256 amount) public virtual returns (bool) {
        _mint(account, amount);
        return true;
    }

    function burn(address account, uint256 amount) public virtual returns (bool) {
        _burn(account, amount);
        return true;
    }
}

And here is the unit testing file

//TestCoin.test.js
// Load dependencies
const { accounts, contract } = require('@openzeppelin/test-environment');
const { BN } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');

// Load compiled artifacts
const TestCoin = contract.fromArtifact('TestCoin');

// Load configured accounts
const [ admin, buyer, seller ] = accounts;

// Initialization test block
describe('TestCoin initialisation', () => {

    it("Name should be 'TestCoin'", async () => {
        const TestCoinInstance = await TestCoin.new();
        name = await TestCoinInstance.name();
        expect(name).to.be.a.string;
        expect(name).to.equal("TestCoin");
    });

    it("Symbol should be 'TSC'", async () => {
        const TestCoinInstance = await TestCoin.new();
        symbol = await TestCoinInstance.symbol();
        expect(symbol).to.be.a.string;
        expect(symbol).to.equal("TSC");
    });

    it('Should have 18 decimals', async () => {
        const TestCoinInstance = await TestCoin.new();
        expect(await TestCoinInstance.decimals()).to.be.bignumber.equal('18');
    });

    it('Total supply should be 10000000000000000000000', async () => {
        const TestCoinInstance = await TestCoin.new();
        expect(await TestCoinInstance.totalSupply()).to.be.bignumber.equal('10000000000000000000000');
    });

});

// Accounts test block
describe('TestCoin accounts', () => {

    it('Balance of admin account should be 10000000000000000000000', async () => {
        const TestCoinInstance = await TestCoin.new({ from: admin });
        expect(await TestCoinInstance.balanceOf(admin)).to.be.bignumber.equal('10000000000000000000000');
    });

    it('Balance of buyer should be 0', async () => {
        const TestCoinInstance = await TestCoin.new({ from: admin });
        expect(await TestCoinInstance.balanceOf(buyer)).to.be.bignumber.equal('0');
    });

    it('Balance of seller should be 0', async () => {
        const TestCoinInstance = await TestCoin.new({ from: admin });
        expect(await TestCoinInstance.balanceOf(seller)).to.be.bignumber.equal('0');
    });
});

// Mint / Burn test block
describe('TestCoin mint / burn', () => {

    it('Mint/Burn test', async () => {
        const TestCoinInstance = await TestCoin.new({ from: admin });

        expect(await TestCoinInstance.balanceOf(admin)).to.be.bignumber.equal('10000000000000000000000');

        // Create 100 TSC on admin account
        await TestCoinInstance.mint(admin, 100);
        expect(await TestCoinInstance.balanceOf(admin)).to.be.bignumber.equal('10000000000000000000100');
        expect(await TestCoinInstance.totalSupply()).to.be.bignumber.equal('10000000000000000000100');

        // Create 100 TSC on seller account
        await TestCoinInstance.mint(seller, 100);
        expect(await TestCoinInstance.balanceOf(seller)).to.be.bignumber.equal('100');
        expect(TestCoinInstance.totalSupply()).to.be.bignumber.equal('10000000000000000000200');

        // Remove 100 TSC from admin account
        await TestCoinInstance.burn(admin, 100);
        expect(await TestCoinInstance.balanceOf(admin)).to.be.bignumber.equal('10000000000000000000000');
        expect(await TestCoinInstance.totalSupply()).to.be.bignumber.equal('10000000000000000000100');

        // Remove 100 TSC from seller account
        await TestCoinInstance.burn(seller, 100);
        expect(await TestCoinInstance.balanceOf(seller)).to.be.bignumber.equal('0');
        expect(await TestCoinInstance.totalSupply()).to.be.bignumber.equal('10000000000000000000000');
    });
});

// Transfers test block
describe('TestCoin accounts', () => {

    it('Transfer 100 TSC to seller', async () => {
        const TestCoinInstance = await TestCoin.new({ from: admin });

        expect(await TestCoinInstance.balanceOf(admin)).to.be.bignumber.equal('10000000000000000000000');

        await TestCoinInstance.transfer(seller, 1);
        //await TestCoinInstance.transferFrom(admin, seller, 1);
        expect(await TestCoinInstance.balanceOf(admin)).to.be.bignumber.equal('9999999999999999999999');
        expect(await TestCoinInstance.balanceOf(seller)).to.be.bignumber.equal('1');
    });
});

By reading the code it seems that the issue is that address spender = _msgSender(); doesn't retrieve the good address so the account balance is zero. However I can't find how to specify that the admin account should make the transfer.

Does anybody knows what I'm missing into my test case ?

Thanks in advance

Hi, welcome to the community! :wave:

I think you can write like this to specify an account:

await TestCoinInstance.transfer(seller, 1, {from: admin});
1 Like

Thanks a lot, it's indeed working :slight_smile: