How do you test a Require statement in a smartcontract using Mocha?

Viewed 166

I am fairly new to developing smartcontracts and have run into an issue while testing. My intention is to ensure the smartcontract cannot mint more than 13 ERC721 tokens. My understanding is that a require function can have a second string argument that will revert a string as an error if the require condition is not met. Is this correct? The following is my smart contract code:

contract TheCondemned_Episode is ERC721Enumerable {

    string[] public episodes;
    

    constructor() ERC721("TheCondemned_e1", "TCe1") public {
    }

    function mint(string memory _episode) public {
       
        require(episodes.length <= 13, "Cannot make more than 13 episodes");
        episodes.push(_episode);
        uint _id= episodes.length;
        _mint(msg.sender, _id);
      
    }
}

And the test I am running is as follows:

it('Cannot create more than 13 episodes', async() => {
            
            
            
            for(var i=0; i===13; i++){
                
                var episode= `Episode ${i}`
                await contract.mint(episode)
            }
            
            try {
                await contract.mint('Episode 14');
                assert(true);
            }
            catch (err) {
                return;
            }
            assert(false, "The contract did not throw.");

The test fails and returns "The contract did not throw". What is the best practice in regards to catching a revert string from a failed require condition when testing?

1 Answers

My understanding is that a require function can have a second string argument that will revert a string as an error if the require condition is not met. Is this correct?

That's correct. Here's an example of an always failing require() condition that throws an exception with the error message.

require(false, 'Error message');

However, you have a logical error in the Solidity require() condition, as well as in the JS test snippet.


First, let's uncover the Solidity code. For simplicity, let's assume you're allowing to mint only 1 episode.

require(episodes.length <= 1, "Cannot make more than 1 episode");
  1. First iteration (expected to pass)

    episodes.length is 0, that's <= 1. Condition passes, you mint the first token, and then push to the episodes array, so its length becomes 1 after the condition.

  2. Second iteration (expected to fail)

    episodes.length is 1, that's still <= 1. So the condition passes as well.

Solution: Replace the <= (less than or equal) to just < (less than).

require(episodes.length < 1, "Cannot make more than 1 episode");
  1. First iteration (expected to pass)

    episodes.length is 0, that's < 1. Condition passes, you mint the first token, and then push to the episodes array, so its length becomes 1 after the condition.

  2. Second iteration (expected to fail)

    episodes.length is 1, which fails the condition 1 < 1, as you expect.


I'm assuming that your intention in the JS snippet is to call the mint() function 13 times in the loop, and then 14th time in the try/catch block.

However, the loop currently doesn't perform any iteration. So in fact, you're only executing the mint() function once (in the try/catch block).

The second parameter in the for loop is a condition stating "this loop will keep iterating for as long as this condition is met". But since you set the value of i to 0 in the first parameter, the loop condition (i===13) is not met, and the loop doesn't perform even the first iteration.

Solution: Check whether the i is "less than 13" instead of just "equals 13".

for(var i = 0; i < 13; i++) {

This way, the loop will iterate 13 times.

Related