'Passing a Struct Array to constructor of Solidity Contract

I am building an NFT smart contract with solidity, and I am trying to pass and Array of Structs into the constructor when I deploy the contract. However I am getting the following error.

TypeError: Cannot read property 'length' of undefined

The contact code is:

contract MetropolisWorldGenesis {

    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    struct PropertyAttributes {
        uint id;
        string name;
        string description;
        string image;
        Properties properties;
    }

    struct Properties {
        string tower;
        string disctrict;
        string neighborhood;
        string primary_type;
        string sub_type_1;
        string sub_type_2;
        string structure;
        string feature_1;
        string feature_2;
        string feature_3;
        string feature_4;
        string feature_5;
        string rarity;
        // string internal_id;
    }

    //store a list of all the NFT's available to mint. 
    //this is built on when the constructor is called. 
    PropertyAttributes[] defaultProperties;

    //store which has been minted. 
    mapping(uint => bool) public MintedNfts;

    //map the nft tokenid to the atributes 
    mapping(uint256 => PropertyAttributes) public nftAttributes;

    constructor(PropertyAttributes[] memory props) { 
        console.log("OK I am making the contract, just this once mind");

        for (uint i = 0; i < props.length; i += 1){
             defaultProperties.push(props[i]);

             PropertyAttributes memory p = defaultProperties[i];
             console.log("Done initializing %s w/ HP %s, img %s", p.name, p.description, p.image);
        
    } 
}

and I am calling it using the following:

const main = async () => {

    // make up the data from t he json 
    const nftList = require('./nft_list_finalv2.json')
    
    let props = []

    for(var i=0; i < nftList['nfts'].length;i+=1){
        
        x = nftList['nfts'][i]['metadata']
        props.push(x)
    }
    
    console.log(props.length)

    // deply the contract will the data made above. 
    const propertyContractFactory = await hre.ethers.getContractFactory('MetropolisWorldGenesis');
    const propertyContract = await propertyContractFactory.deploy(
        props
    );
    await propertyContract.deployed();
    console.log("Contract deployed to:", propertyContract.address);
  };
  
  const runMain = async () => {
    try {
      await main();
      process.exit(0);
    } catch (error) {
      console.log(error);
      process.exit(1);
    }
  };
  
  runMain();

The Json file is an arrary of items structured as follows.

{ 'nfts':[
     {
            "id": 1,
            "metadata": {
                "id": 1,
                "name": "tester",
                "description": "Rtestt",
                "image": "",
                "properties": {
                    "tower": "Tower 1",
                    "district": "Hir",
                    "neighborhood": "Fres",
                    "primary_type": "Whause",
                    "sub_type_1": "Aboned",
                    "sub_type_2": "Fors",
                    "structure": "Dark brick",
                    "feature_1": "Df",
                    "feature_2": "Gins",
                    "feature_3": "Castes",
                    "feature_4": "Cloors",
                    "feature_5": "e walls",
                    "rarity": "",
                    "internal_id": "Tower 1_1"
                }
            },
            "price": 10,
            "ipfs": "",
            "img_name": "WqmYMT02j.png",
            "map_ref": "Z"
        },
....
]}

I get the array of data fine on javascript side but seems to be some error as i pass it into the contract. What am i missing here?



Solution 1:[1]

Last time I checked there was no way to pass a struct to a constructor function (see here this 2018 answer). It says that:

...you cannot pass struct to a constructor as it will be receiving its input outside from the blockchain. Only the private and internal functions can expect structs as input parameters.

The solution is to use "primitive" data types as inputs. For your particular case the constructor function could be modified to accept one NFT object like:

constructor (
  int NFTsId,
  int MetaId,
  string MetaName,
  string MetaDescription,
  string MetaImage,
  string PropertiesTower,
  string PropertiesDistrict,
  ...,
  uint MetaPrice,
  string MetaIPFS,
  ...
) {
  // Assign values
}

Or you can write the constructor function to receive multiple inputs in arrays and relocate them according to their index position (the first MetaId goes with the first MetaName...):

constructor (
  int[] memory NFTsIds,
  int[] memory MetaIds,
  string[] memory MetaNames,
  string[] memory MetaDescriptions,
  string[] memory MetaImages,
  string[] memory PropertiesTowers,
  string[] memory PropertiesDistricts,
  ...,
  uint[] memory MetaPrices,
  string[] memory MetaIPFSs,
  ...
) {  
  for (uint256 i=0; i < NFTsId.length; i ++){
    NFTsId = NFTsIds[i];
    ...
    // Assign values
  }
}

It is important to point out that the for loop that would consume considerable amounts of gas depending on the amount of NFTs objects send to the constructor.

Solution 2:[2]

Actually, you can in fact pass a struct as an arg to a constructor in solidity. I'm doing it myself in one of my contracts:


struct UserConfig {
        address user;
        address userToken;
        uint userSlippage; 
    }

    struct FixedConfig { 
        address inbox;
        address ops;
        address PYY;
        address emitter;
        uint maxGas;
    }

FixedConfig fxConfig;
VariableConfig varConfig;


constructor(
        FixedConfig memory fxConfig_,
        VariableConfig memory varConfig_
    ) {
        fxConfig = fxConfig_;
        varConfig = varConfig_;
    }

You'd pass it as an array on ethers.js.

The problem that I'm having, and how I ended up in this post in the first place, is that when you pass the array to deploy() on ethers, it changes the order of the vars on the struct in the contract. So I'm trying to figure out why that's happening.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nico Serrano
Solution 2 dNyrM