Search code examples
solidityethers.jshardhatrsk

How to test the Solidity fallback() function via Hardhat?


I have a Solidity smart contract Demo which I am developing in Hardhat and testing on the RSK Testnet.

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

contract Demo {
    event Error(string);
    fallback() external {
      emit Error("call of a non-existent function");
    }
}

I want to make sure that the fallback function is called and the event Error is emitted. To this end, I am trying to call a nonExistentFunction on the smart contract:

const { expect } = require('chai');
const { ethers } = require('hardhat');

describe('Demo', () => {
  let deployer;
  let demoContract;
    
  before(async () => {
    [deployer] = await ethers.getSigners();
    const factory = await ethers.getContractFactory('Demo');
    demoContract = await factory.deploy().then((res) => res.deployed());
  });
    
  it('should invoke the fallback function', async () => {
    const tx = demoContract.nonExistentFunction();
    await expect(tx)
      .to.emit(demoContract, 'Error')
      .withArgs('call of a non-existent function');
  });
});

However Hardhat throws a TypeError even before it actually connects to the smart contract on RSK:

  Demo
    1) should invoke the fallback function


  0 passing (555ms)
  1 failing

  1) Demo
       should invoke the fallback function:
     TypeError: demoContract.nonExistentFunction is not a function
      at Context.<anonymous> (test/Demo.js:13:29)
      at processImmediate (internal/timers.js:461:21)

How can I outsmart Hardhat/Ethers.js and finally be able to call non-existent function thus invoking the fallback function in the smart contract?

For reference, this is my hardhat.config.js

require('@nomiclabs/hardhat-waffle');
const { mnemonic } = require('./.secret.json');

module.exports = {
  solidity: '0.8.4',
  networks: {
    hardhat: {},
    rsktestnet: {
      chainId: 31,
      url: 'https://public-node.testnet.rsk.co/',
      accounts: {
        mnemonic,
        path: "m/44'/60'/0'/0",
      },
    },
  },
  mocha: {
    timeout: 600000,
  },
};

Solution

  • You can use the approach of injecting a non-existent function signature into the smart contract object (ethers.Contract). Create a function signature for nonExistentFunction:

    const nonExistentFuncSignature =
      'nonExistentFunction(uint256,uint256)';
    

    Note that the parameter list should contain no whitespace, and consists of parameter types only (no parameter names).

    Then instantiate a new smart contract object.

    When you do this you need to modify the ABI for Demo such that it includes this additional function signature.:

    const fakeDemoContract = new ethers.Contract(
      demoContract.address,
      [
        ...demoContract.interface.fragments,
        `function ${nonExistentFuncSignature}`,
      ],
      deployer,
    );
    

    Note that the contract that is deployed is the same as before - it does not contain this new function. However the client interacting with this smart contract - the tests in this case - does think that the smart contract has this function now.

    At this point, you'll be able to run your original test, with a minor modification:

    const tx = fakeDemoContract[nonExistentFuncSignature](8, 9);
    await expect(tx)
      .to.emit(demoContract, 'Error')
      .withArgs('call of a non-existent function');
    

    Full test:

    it('should invoke the fallback function', async () => {
        const nonExistentFuncSignature = 'nonExistentFunc(uint256,uint256)';
        const fakeDemoContract = new ethers.Contract(
          demoContract.address,
          [
            ...demoContract.interface.fragments,
            `function ${nonExistentFuncSignature}`,
          ],
          deployer,
        );
        const tx = fakeDemoContract[nonExistentFuncSignature](8, 9);
        await expect(tx)
          .to.emit(demoContract, 'Error')
          .withArgs('call of a non-existent function');
     });
    

    Test result:

      Demo
        ✔ should invoke the fallback function (77933ms)
    
    
      1 passing (2m)