Factory Design Pattern with Solidity

The Factory design pattern is the most widely used design pattern from the popular 23 “Gang of Four” design patterns. The pattern designates that you define an interface or an abstract class for creating an object but you let the subclasses determine what class will be instantiated. The logic that creates the object isn’t exposed to the client; in this case you only need to know what parameters are required for the NFT constructor.

NftFactory contract deploys a new NFT without revealing how to the client, using the IFactoryItem interface, which is the primary basis for the factory design pattern. In Solidity the contract acts like a class to make it possible to inherit a common interface and make all this fantastic awesomeness possible.

The new NFT contract addresses can now be mapped and accounted for via the spawnedNftMapping solidity mapping example.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import './interface/IFactoryItem.sol';
import "@openzeppelin/contracts/access/Ownable.sol";

contract NftFactory is Ownable {

    event NftSpawned(address newNft, address _owner);

    address[] public spawnedNftArray;
    mapping(address => bool) public spawnedNftMapping;

    function spawn(
        address nftToSpawn,
        string calldata name,
        string calldata symbol,
        string calldata uri,
        bytes calldata data
    ) external returns (address){

        address nft = IFactoryItem(nftToSpawn).spawnNft(
            name,
            symbol,
            uri,
            data
        );

        emit NftSpawned(nft, msg.sender);
        return nft;
    }

}

NftFactoryItemERC721 references the BasicERC721 contract and deploys it, returning the address of the new NFT. The NFT token contract is the application on the blockchain that handles minting NFTs and other administration tasks. The two concepts are sometimes muddled together.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import './interface/IFactoryItem.sol';
import './BasicERC721.sol';

contract NftFactoryItemERC721 is IFactoryItem{
     
    function spawnNft(
        string calldata name,
        string calldata symbol,
        string calldata uri,
        bytes calldata
    )external returns(address){
        BasicERC721 nftToken = new BasicERC721(name,symbol,uri);
        return address(nftToken);

    }
 }

A basic ERC721 ( BasicERC721.sol ) extends the contract implementation from open zeppelin. My basic NFT contract also extends Ownable; the owner() function that is called in the initial test is derived from the the Ownable contract.

The BasicERC721 Nft contract doesn’t do much here, it’s just a very popular subject and good example to use to talk about a factory pattern with solidity. Save the Counter contract for later.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract BasicERC721 is ERC721, Ownable {

    string public uri;

    constructor(
        string memory _name,
        string memory _symbol,
        string memory _uri) 
        ERC721(_name,_symbol) {
            uri = _uri;
        }
    
}

The IFactoryItem interface is instantiated from NftFactoryItemERC721 that deploys a new NFT smart contract Object to the Ethereum Virtual Machine.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

 interface IFactoryItem{

    function spawnNft(
        string calldata name,
        string calldata symbol,
        string calldata uri,
        bytes calldata data
    )external returns(address);
 }

Testing the NftFactory with some basic tests, i.e. the contract is deployed by the owner. We don’t have an owner() function in our Basic NFT, we’re getting that from our parent class from OZ and accessing the _owner address member variable that was defined there. The nft contract is spawned by the Factory and for now we’re just spitting out the receipt to the console.

const { accounts, contract } = require('@openzeppelin/test-environment');
const { expect } = require('chai');

describe('NftFactory', function () {

  const [owner, addr1] = accounts;

  const NftFactory = contract.fromArtifact('NftFactory');
  const NftFactoryItemERC721 = contract.fromArtifact('NftFactoryItemERC721');

  beforeEach('Deploy NftFactory and NftFactoryItemERC721', async function () {
    this.nftFactory = await NftFactory.new({ from: owner });
    this.nftItem = await NftFactoryItemERC721.new({ from: owner });
  });

  describe('NftFactory Owner', function () {
    
    it('NftFactory contract owner is sender', async function () {
      expect(await this.nftFactory.owner()).to.equal(owner);
    });

  });

  describe('Spawn Nft with NftFactory', function () {

      it('Spawn New Nft', async function(){
        const receipt = await this.nftFactory.spawn(
          this.nftItem.address, 
          'MyNFT', 
          'MNFT', 
          'https://baseURI',
          [],
          { from: addr1 });

          console.log(receipt.logs);
      });

  });

});

repo branch: https://github.com/bws9000/smart-contract-tests/tree/basic-factory-pattern

In the next article solidity-smart-contracts/a-basic-nft/ we’re going to update the BasicERC721 contract to make it look more like an NFT.

By Burt Snyder

Writing about interesting things and sharing ideas.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.