0

My contract inherits the ownable.sol which has a mint() method that is marked onlyOwner like below:

 function mint(address _to, string memory newTokenURI) public onlyOwner 

I know that within the ownable if the owner check fails it would return a rejected message "Ownable: caller is not the owner". I am trying to test my mint function's user access to do full test coverage. Note: I am using the chai-matchers library

describe("EFTContract", function () {
  let token721;
  let TEST_URI1 = "ipfs://tokenurifor1";
  beforeEach(async function () {
    EFT721Contract = await ethers.getContractFactory("EFTNFT");
    [owner, account1, ...otheraccounts] = await ethers.getSigners();
    token721 = await EFT721Contract .deploy();
  });

  
  describe("Ownership Tests", function () {
    // ok
    it("Testowner can call mint()", async function () {
      expect(await token721.mint(account1.address, TEST_URI1)).to.not.be.reverted;
    });
    // fails
    it("Test guest should not call mint()", async function () {
      // mint 1 so we have a valid token to try to query with tokenURI()
      await token721.mint(account1.address, TEST_URI1);
      // connect to guest account to try and mint
      await expect(token721
        .connect(account1.address)
        .mint(account1.address, TEST_URI1))
        .to.be.rejectedWith("Ownable: caller is not the owner");
    });'
  });

When I run this the first test passes but the second fails with the following error:

"VoidSigner cannot sign transactions (operation=\"signTransaction\", code=UNSUPPORTED_OPERATION, version=abstract-signer/5.6.2)"

I know I can replace that assert to pass the test replacing it with the code below but I believe that isnt properly testing ownership access of the ownable.sol contract:

 await expect(token721
        .connect(account1.address)
        .mint(account1.address, TEST_URI1))
        .eventually.to.rejectedWith(Error, "VoidSigner cannot sign transactions (operation=\"signTransaction\", code=UNSUPPORTED_OPERATION, version=abstract-signer/5.6.2)");

How can I instead modify the second test case to properly compare against `"Ownable: caller is not the owner"?

Solution: It looks like I should wait for the promise to resolve before comparing the results. In this case it would provide an error instead of a rejected messaged so I should use .to.be.revertedWith() instead.

    // mint
    it("Testowner can call mint()", async function () {
      expect(await token721.mint(account1.address, TEST_URI1)).to.not.be.reverted;
    });

    it("Test guest should not call mint()", async function () {
      await expect(token721
        .connect(account1)
        .mint(account1.address, TEST_URI1))
        .to.be.revertedWith("Ownable: caller is not the owner");
    });

2 Answers 2

1

The await keyword is at the wrong place. Replace this part:

      await expect(token721
        .connect(account1.address)
        .mint(account1.address, TEST_URI1))
        .to.be.rejectedWith("Ownable: caller is not the owner");

Like this:

      expect(await token721
        .connect(account1.address)
        .mint(account1.address, TEST_URI1))
        .to.be.rejectedWith("Ownable: caller is not the owner");

Make sure to execute the expect() after the await.

Update: It's also because you're connecting to the account the wrong way. Don't do this:

.connect(account1.address)

Do this:

.connect(account1)

8
  • 1
    I tried that and It fails with the same message Error: voidSigner cannot sign transactions.
    – Potion
    Commented Aug 19, 2022 at 20:18
  • Then that's because you're using .connect(account1.address) which is incorrect you don't want to access the address when connecting so this is the right way: .connect(account1) Commented Aug 19, 2022 at 20:21
  • That makes sense. So the message improved. But it errors prior to to comparing the string. Currently it returnesError: VM Exception while processing transaction: reverted with reason string 'Ownable: caller is not the owner'. Do I need a try catch to capture the error and compare against? The chai-matchers library should handle this but I dont see anything about it in their documentation
    – Potion
    Commented Aug 19, 2022 at 20:42
  • After a little more searching error is caught with .to.be.revertedWith instead of rekectedWith. Finally the promise should resolve before the expect otherwise we cant compare capture the error to compare against. So in this case it would be await before expect. Can you modify your answer so I can accept?
    – Potion
    Commented Aug 19, 2022 at 20:49
  • If you test with Foundry you're a lot less likely to make these mistakes @Potion , since the tests are written in Solidity.
    – Ryan Sea
    Commented Aug 19, 2022 at 20:59
1

I don't know if @Merunas G's answer was on a different version of Hardhat Ethers (*), but moving the await didn't work for me. This should work (note the use of reverted as was discussed in @Merunas's answer's comments):

await expect(token721
        .connect(account1.address)
        .mint(account1.address, TEST_URI1))
        .to.be.revertedWith("Ownable: caller is not the owner");

However, the Ownable abstract contract uses a Custom Error (https://hardhat.org/hardhat-chai-matchers/docs/overview#reverts) so the answer (at least today - *) is:

await expect(token721
        .connect(account1.address)
        .mint(account1.address, TEST_URI1))
        .to.be.revertedWithCustomError(token721, "OwnableUnauthorizedAccount");
    • ethers6 introduced a lot of breaking changes

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.