I'm working on a project using Hardhat and OpenZeppelin library for an ERC20 token. I've been able to achieve almost 100% coverage, but there are two lines that I'm struggling to cover:

  • transfer where the from is the zero/null address.
  • approve where the owner is the zero/null address.

I haven't been able to find a way to "impersonate" the zero/null address in my tests to cover these scenarios and achieve 100% coverage. I've tried various approaches, but none of them seem to work. I know that these scenarios are super edge cases and may not be critical to test. However, I'm trying to achieve 100% test coverage. This issue has piqued my curiosity, and I'm eager to understand how to do it.

Here's an example of the code I'm trying to cover:


function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address"); // <--- This is the line I want to cover with a test
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);


function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address"); // <--- This is the line I want to cover with a test
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);

This is an example of a test to cover transfer to the zero address

    it('reverts when transfer to the zero address', async () => {
      const { contract } = await loadFixture(deployUSDMFixture);
      const { AddressZero } = ethers.constants;
      const amount = 1;

      await expect(contract.transfer(AddressZero, amount))
        .to.be.revertedWithCustomError(contract, 'ERC20InvalidReceiver')
  • But why would zero address be the sender ever? I don't find any basis of this test, what is it for? Commented Jun 5, 2023 at 13:41
  • I'm trying to achieve 100% test coverage and it has piqued my curiosity. I wanna know if there is any way to achieve this
    – Mati
    Commented Jun 5, 2023 at 13:54
  • No ser, this doesn't really makes sense. I mean how a zero address can sign a tx ? if it can then this would never be used to burn millions of dollars. So there isn't any case where 0 address will be the signer and you don't need to put a require statement for that. Commented Jun 5, 2023 at 17:25
  • As I said, I know it doesn't make sense, just trying to achieve 100% coverage
    – Mati
    Commented Jun 5, 2023 at 22:11

2 Answers 2


I think you can do it by impersonating the zero address using hardhat's trick to fake a signer https://hardhat.org/hardhat-network/docs/guides/forking-other-networks#impersonating-accounts

Like this (I haven't tested though):

const { AddressZero } = ethers.constants;
const signerZero = await ethers.getImpersonatedSigner(AddressZero);
await expect(contract.connect(signerZero).transfer(AddressZero, amount))
        .to.be.revertedWithCustomError(contract, 'ERC20InvalidReceiver')
  • I will try with getImpersonatedSigner. I've tried helpers.impersonateAccount but I haven't tried this one
    – Mati
    Commented Jun 5, 2023 at 22:14

Found a way to do it! The following test checks if an ERC20 token contract correctly rejects a transfer from the Zero address. It impersonates the zero address, funds it with Ether for paying the transfer transaction gas, and then attempts a token transfer, expecting a revert:

it('reverts when transfer from the zero address', async () => {
  const { contract, owner } = await loadFixture(deployTokenFixture);
  const signerZero = await ethers.getImpersonatedSigner(AddressZero);

  // Fund the zero address to pay for the transaction
  await owner.sendTransaction({
    to: signerZero.address,
    value: ethers.utils.parseEther('1') // Send 1 ETH

  await expect(contract.connect(signerZero).transfer(signerZero.address, 1))
    .to.be.revertedWith('ERC20: transfer from the zero address');

Hope it's useful for you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.