Skip to main content

Unit Tests

Testing solidity libraries with structs

When we are testing a solidity library and there is a function that receives a struct as an argument, a CALL_EXCEPTION error may occur.

For example:

library DynamicMetadata {

struct Attribute {
string displayType;
string traitType;
string value;
}

function toBytes(DynamicMetadata.Attribute calldata attribute) public pure returns (bytes memory) {
...
}

}

In this case, we need to test the toBytes function in Javascript using hardhat:

...

const DynamicMetadata = await ethers.getContractFactory('DynamicMetadata', owner);
const dynamicMetadataLibrary = await DynamicMetadata.deploy();
await dynamicMetadataLibrary.deployed();

const attr = {
displayType: 'a',
traitType: 'b',
value: 'c',
};

const attributesInBytes = await dynamicMetadataLibrary.toBytes(attr);
expect(attributesInBytes).to.equal(EXPECTED_VALUE_IN_BYTES);

...

But for some reason (maybe a bug), it throws a CALL_EXCEPTION error. Apparently, this only occurs with functions with structs parameters in libraries.

A workaround solution is to create a test contract that imports the library and "overrides" the functions that we want to test:

import '../libraries/DynamicMetadata.sol';

contract DynamicMetadataTestContract {

function toBytes(DynamicMetadata.Attribute calldata attribute) public pure returns (bytes memory) {
return DynamicMetadata.toBytes(attribute);
}

}

And write the test case using the new contract:

...

const DynamicMetadata = await ethers.getContractFactory('DynamicMetadata', owner);
const dynamicMetadataLibrary = await DynamicMetadata.deploy();
await dynamicMetadataLibrary.deployed();

const DynamicMetadataTestContract = await ethers.getContractFactory(
'DynamicMetadataTestContract',
{
signer: owner,
libraries: {
DynamicMetadata: dynamicMetadataLibrary.address,
},
},
);
dynamicMetadataTestContract = await DynamicMetadataTestContract.deploy();
await dynamicMetadataTestContract.deployed();

const attr = {
displayType: 'a',
traitType: 'b',
value: 'c',
};

const attributesInBytes = await dynamicMetadataTestContract.toBytes(attr);
expect(attributesInBytes).to.equal(EXPECTED_VALUE_IN_BYTES);

...

Now we can test the library successfully.

Testing internal functions in solidity contracts

When we want to test internal functions in solidity, for example:

contract GM721 is ERC721, ERC721Burnable {
...

function _beforeBatchMint(address, uint256[] calldata) internal virtual {
...
}

function _batchMint(address to, uint256[] calldata tokenIds) internal {
_beforeBatchMint(to, tokenIds);

...
}

...
}

We have to create a test contract that implements the contract that we want to test:


contract TestGM721 is GM721 {
bool public beforeBatchMintExecuted = false;

...

function _beforeBatchMint(address to, uint256[] calldata tokenIds) internal override(GM721) {
beforeBatchMintExecuted = true;
super._beforeBatchMint(to, tokenIds);
}

function batchMint(address to, uint256[] calldata tokenIds) external {
super._batchMint(to, tokenIds);
}

...
}

In this case, we want to test the batchMint() function and verify that the _beforeBatchMint() is called. So we define a beforeBatchMintExecuted flag that we can use to check if the function was executed.

Our test ends up looking like this:

...
expect(await testGM721.beforeBatchMintExecuted()).to.be.false;
await testGM721.batchMint(owner.address, [1, 2, 3]);
expect(await testGM721.beforeBatchMintExecuted()).to.be.true;
...