Invoking _mint function in ERC721 is reverted from Mocha javascript test

Invoking super._mint function from Mocha javascript test case is reverted and I suspect _ownedTokensCount[to].increment(); in ERC721contract from "../../drafts/Counters.sol"; library might have some negative influence on it due to the linkage problem at deployment which is mentioned here:

I’ve already checked from Truffle debug that msg.sender and tokenId are properly allocated inside contracts and also super._mint function properly works from the CLI.

A contract

pragma solidity ^0.5.0;
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721Full.sol";
import "@openzeppelin/upgrades/contracts/Initializable.sol";


contract ABNMarket is Initializable, ERC721Full {

    constructor() public ERC721Full(){
        // here goes the constructor
    }

    string public tokenName;
    uint256 internal nextTokenId = 1;

    function mint() public returns (){
        uint256 tokenId = nextTokenId;
        super._mint(msg.sender, tokenId);
    }

    function initialize(string memory _tokenName) public {
        tokenName = _tokenName;
    }
}

A javascript Mocha test code (Not all)

const ABNToken = Contracts.getFromLocal('ABNToken');

  beforeEach(async function () {
    this.project = await TestHelper();

    proxy = await this.project.createProxy(ABNToken, { initMethod: "initialize", initArgs: ["CLR"] });
  })

describe('Mint Function Test', () => {
    it('Check mint function', async () => {
      await proxy.methods.mint().send({from: sender});
    })
  })

My basic setting environment is as follows:

Truffle v5.0.30 (core: 5.0.30)
Solidity v0.5.0 (solc-js)
Node v11.14.0
Web3.js v^1.2.0

"@openzeppelin/cli": "^2.5.2",
"@openzeppelin/contracts-ethereum-package": "^2.2.1",
"@openzeppelin/upgrades": "^2.5.2",

Any suggestion or advise is helpful and appreciated.

2 Likes

Hi @toshiboy86
Welcome to the community :wave:

I spotted a few issues, detailed below:

ABNMarket.sol

I removed returns () from the mint function in order to compile.

Due to a requirement of the proxy-based upgradeability system, no constructors can be used in upgradeable contracts: https://docs.openzeppelin.com/sdk/2.5/pattern#the-constructor-caveat The constructor should be an initializer function.

Solidity allows defining initial values for fields when declaring them in a contract.
This is equivalent to setting these values in the constructor, and as such, will not work for upgradeable contracts: https://docs.openzeppelin.com/sdk/2.5/writing-contracts#avoid-initial-values-in-field-declarations This should be done in an initializer function.

Currently tokenId is not incremented. So the `tokenId is always being set to 0, as the initial value can’t be defined in an upgradeable contract.

Also the mint function is public with no restriction on who can call it, I am not sure if this is your intention.

pragma solidity ^0.5.0;
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721Full.sol";
import "@openzeppelin/upgrades/contracts/Initializable.sol";


contract ABNMarket is Initializable, ERC721Full {

    constructor() public ERC721Full(){
        // here goes the constructor
    }

    string public tokenName;
    uint256 internal nextTokenId = 1;

    function mint() public {
        uint256 tokenId = nextTokenId;
        super._mint(msg.sender, tokenId);
    }

    function initialize(string memory _tokenName) public {
        tokenName = _tokenName;
    }
}

Create

When I create your contract, there are a number of warnings for issues described above.:
Contract ABNMarket has an explicit constructor
Contract ABNMarket or one of its ancestors sets an initial value in a field declaration

$ npx oz create
Nothing to compile, all contracts are up to date.
? Pick a contract to instantiate ABNMarket
? Pick a network development
✓ Added contract ABNMarket
✓ Deploying @openzeppelin/contracts-ethereum-package dependency to network dev-1566901042319
- Contract ABNMarket has an explicit constructor. Change it to an initializer function. See https://docs.openzeppelin.com/sdk/2.5/writing_contracts.html#initializers.
- Contract ABNMarket or one of its ancestors sets an initial value in a field declaration. Consider moving all field initializations to an initializer function. See https://docs.openzeppelin.com/sdk/2.5/writing_contracts.html#avoid-initial-values-in-fields-declarations.
✓ Contract ABNMarket deployed
All contracts have been deployed
? Do you want to call a function on the instance after creating it? Yes
? Select which function initialize(_tokenName: string)
? _tokenName (string): ABNMarket
✓ Setting everything up to create contract instances
✓ Instance created at 0xf19A2A01B70519f67ADb309a994Ec8c69A967E8b
0xf19A2A01B70519f67ADb309a994Ec8c69A967E8b

ABNMarket.test.js

Modifying the test slightly to await proxy.methods.mint(); allows it to pass.

const { TestHelper } = require('@openzeppelin/cli');
const { Contracts, ZWeb3 } = require('@openzeppelin/upgrades');

ZWeb3.initialize(web3.currentProvider);

const abnMarket = Contracts.getFromLocal('ABNMarket');

require('chai').should();

contract('ABNMarket', function () {

  beforeEach(async function () {
    this.project = await TestHelper();

    proxy = await this.project.createProxy(abnMarket, { initMethod: "initialize", initArgs: ["CLR"] });
  })

  describe('Mint Function Test', () => {
    it('Check mint function', async () => {
      await proxy.methods.mint();
    })
  })
})

A potential upgradeable ERC721 contract could look as follows:

ABNMarket.sol

import "@openzeppelin/contracts-ethereum-package/contracts/drafts/Counters.sol";
import "@openzeppelin/upgrades/contracts/Initializable.sol";

contract ABNMarket is Initializable, ERC721Full, MinterRole
{
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    function initialize(string memory name, string memory symbol, address minter) public initializer {
        ERC721.initialize();
        ERC721Enumerable.initialize();
        ERC721Metadata.initialize(name, symbol);
        MinterRole.initialize(minter);
    }

    function mint(address to, string memory tokenURI) public onlyMinter returns (uint256) {
        _tokenIds.increment();

        uint256 newTokenId = _tokenIds.current();
        _mint(to, newTokenId);
        _setTokenURI(newTokenId, tokenURI);

        return newTokenId;
    }
}

Create

$ npx oz create
✓ Compiled contracts with solc 0.5.11 (commit.c082d0b4)
? Pick a contract to instantiate ABNMarket
? Pick a network development
✓ Deploying @openzeppelin/contracts-ethereum-package dependency to network dev-1566971296954
✓ Contract ABNMarket deployed
All contracts have been deployed
? Do you want to call a function on the instance after creating it? Yes
? Select which function * initialize(name: string, symbol: string, minter: address)
? name (string): My Token
? symbol (string): TKN
? minter (address): 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
✓ Setting everything up to create contract instances
✓ Instance created at 0xA57B8a5584442B467b4689F1144D269d096A3daF
0xA57B8a5584442B467b4689F1144D269d096A3daF

Mint

$ npx oz send-tx
? Pick a network development
? Pick an instance ABNMarket at 0xA57B8a5584442B467b4689F1144D269d096A3daF
? Select which function mint(to: address, tokenURI: string)
? to (address): 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0
? tokenURI (string): https://game.example/item-id-8u5h2m.json
✓ Transaction successful. Transaction hash: 0xaa4d305a8968698ec3444ee967b6c454789e234d51aa999c7de5344bf524cb6d
Events emitted:
 - Transfer(0x0000000000000000000000000000000000000000, 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0, 1)

Get TokenURI

$ npx oz call
? Pick a network development
? Pick an instance ABNMarket at 0xA57B8a5584442B467b4689F1144D269d096A3daF
? Select which function tokenURI(tokenId: uint256)
? tokenId (uint256): 1
✓ Method 'tokenURI(uint256)' returned: https://game.example/item-id-8u5h2m.json
https://game.example/item-id-8u5h2m.json

Hi @toshiboy86

Did you have any more questions?
Assuming my reply answered your question, you can mark it as the solution.

Sorry for late response and thank your for your support.

Although ABNMarket.test.js passed correctly following your advise but still when I was going to get data from test code, it was reverted.
The code I added and reverted was below:

ABNMarket.test.js

  describe('Mint Function Test', () => {
    it('Get mint function owner', async () => {
# also replacing this argument 1 with 0 did not work either.
      let _res = await proxy.methods.ownerOf(1).call();
      console.log(_res);
    })
  })

Also upgradeable ABNMarket.sol worked from OZ cli but not from js test code when I tried to get ownerOf(), which is the same phenomenon I described above actually…

ABNMarket.sol

// ABNMarket.sol
import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721Full.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/drafts/Counters.sol";
import "@openzeppelin/upgrades/contracts/Initializable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/access/roles/MinterRole.sol";

contract ABNMarket is Initializable, ERC721Full, MinterRole
{
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    function initialize(string memory name, string memory symbol, address minter) public initializer {
        ERC721.initialize();
        ERC721Enumerable.initialize();
        ERC721Metadata.initialize(name, symbol);
        MinterRole.initialize(minter);
    }

    function mint(address to, string memory tokenURI) public onlyMinter returns (uint256) {
        _tokenIds.increment();

        uint256 newTokenId = _tokenIds.current();
        _mint(to, newTokenId);
        _setTokenURI(newTokenId, tokenURI);

        return newTokenId;
    }
}
1 Like

Hi @toshiboy86

Can you share ABNMarket.test.js? In the shared snippet I don’t see a call to mint.

I can test the contract manually using the OpenZeppelin CLI though I haven’t got the configuration working for tests yet.

Sorry for late.
This actually works. Thank you for your advise.

const ABNMarketContract = artifacts.require("ABNMarket")
const assert = require('assert')

 // argument using mint function
let mintAddress1 = "0x27cd7f93ad0e69f085ba60d405fdc631ef834af7";
let mintAddress2 = "0x27cd7f93ad0e69f085ba60d405fdc631ef834af6";
let tokenURI1 = "here is uri1";
let tokenURI2 = "here is uri2";


contract("ABNMarket", () => {
    let ABNMarket;
    before( async () => {
        ABNMarket = await ABNMarketContract.new()
        console.log(ABNMarket)
    })

    describe('Mint Function', () => {
        it("it should return the tokenId", async () => {
            await ABNMarket.mint.sendTransaction(mintAddress1,tokenURI1);
            let result = await ABNMarket.ownerOf(1)
            console.log(result)
            assert.equal(result, mintAddress1);
        });
        it("it should return the tokenId", async () => {
            await ABNMarket.mint.sendTransaction(mintAddress2,tokenURI2);
            let result = await ABNMarket.ownerOf(2)
            assert.equal(result, mintAddress2);
        });
    })
})
1 Like

Hi @toshiboy86
Glad you were able to resolve.