Accessing totalSupply() in OpenZepelin and Solidity
Lately, I’ve been diving into blockchain development. Over the past few days, I’ve been following along with a YouTube tutorial on minting NFTs. Through the course, I realized this particular tutorial is about 3 years old and some best practices have been updated. In this post, I will be focusing on the Solidity code needed to override some OpenZeppelin functions.
The Error
In general, writing tests for your code is a great idea. This is especially true for blockchain developers due to the immutable nature of blockchain. I use MochaJs, Chai(for the assert keyword), and Truffle in my terminal. When writing a test to mint ERC721 coins with unique colors, I wrote something like this in Solidity: (note: all code snippets begin at the first line of the file)
pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract Color is ERC721 { string[] public colors; mapping(string => bool) _colorExists; constructor() ERC721("Color", "CLR") public { } function mint(string memory _color) public { require(!_colorExists[_color]); colors.push(_color); uint _id = colors.length; _mint(msg.sender, _id); _colorExists[_color] = true; } }
Here, we are importing the OpenZeppelin library, creating a contract with Solidity and referencing the OZ ERC721 coin. Our mint() function is requiring unique colors to be minted (otherwise it wouldn’t be an ERC721), storing the color passed to the function in our colors[] array, assigning an id and minting the coin color. That’s all pretty straightforward. Let’s take a look at our test suite.
const Color = artifacts.require('./Color.sol') require('chai').use(require('chai-as-promised')).should() contract('Color', (accounts) => { let contract before(async () => { contract = await Color.deployed() }) describe('deployment', async () => { it('deploys successfully', async () => { const address = contract.address assert.notEqual(address, 0x0) assert.notEqual(address, '') assert.notEqual(address, null) assert.notEqual(address, undefined) }) it('has a name', async () => { const name = await contract.name() assert.equal(name, 'Color') }) it('has a symbol', async () => { const symbol = await contract.symbol() assert.equal(symbol, 'CLR') }) }) describe('minting', async () => { it('creates new token', async () => { const result = await contract.mint('#EC058E') const totalSupply = await contract.totalSupply() //Success assert.equal(totalSupply, 1) }) })
In our test suite using MochaJS and Chai, we are performing tests to make sure the coin has been deployed with a name. Let’s focus on describe(‘minting’) near the end of the file. Because our Color contract is inheriting functions from the ERC721 library in OZ, we can use the totalSupply() function to keep track of the total amount of coins minted. However, the convention above is deprecated, giving us this error in the console.
After spending hours on different message boards, I finally found a work around that comes from the OZ quick contracts.
pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; contract Color is ERC721, ERC721Enumerable { string[] public colors; mapping(string => bool) _colorExists; constructor() ERC721("Color", "CLR") public { } function mint(string memory _color) public { require(!_colorExists[_color]); colors.push(_color); uint _id = colors.length; _mint(msg.sender, _id); _colorExists[_color] = true; } //Required overrides for new version of OpenZeppelin: function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) { super._beforeTokenTransfer(from, to, tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) { return super.supportsInterface(interfaceId); } //When called after mint(), totalSupply() can now be called in test }
Above is the contract in final form. With the new conventions of the ERC721 contract we are working with, we have to import the ERC721Enumerable extension to gain access to the totalSupply() function. But just importing a second library doesn’t get our tests passing just yet. We have to override both _beforeTokenTransfer(to, from, tokenId) and supportsInterface(interfaceId) and call super on both in the body of the function. After overriding these two functions, we now have access to totalSupply() and our minting tests are passing!
In this article, we updated minting conventions to the modern release of OpenZeppelin and Solidity. My hope is this post will save you some time and frustration if you encounter the same issues. Thanks for reading!