0

I have an issue with my code, I want to create a function to be able to Mint a token, and add a struct to represent all attributes of this token.

The issue is, I have lot's of args on my function (8) and I have to compute new local variables (2) in the function (like tokenId)..

I know that the maximum stack for local variables are 16 or 17 but in my case I have less then that (If i understand well, I have 10).. So it not should be an issue ? (See: Error while compiling: Stack too deep)

I also learn about Diamond Cut paterns who consist to create sub struct, but in my case it'll only create more local variable to create sub-struct. (See Blog Post)

I also ear it's not a good practice to put Struct as function argument, so it's possible to have a function who create a struct with that much arguments or it's just impossible with current version of Solidity (0.8.12) ??

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

  struct TokenData {
    uint tokenId; 

    uint16 familyId;
    uint familySupplyIdx; 

    uint8 typeId;      
    uint16 evoId;        
    uint32 colorId; 
  
    uint16[] traitIds;     
    uint16[4] specsIds;

    uint32 itemId; 

    uint valueA;
    uint valueB;

    uint16[] buffIds;        

    address lockedBy; 
  }


  function mint(uint16 _familyId, uint8 _typeId, uint16 _evoId, uint32 _colorId, uint16[] memory _traitIds, uint16[4] memory _specsIds, uint _valueA, uint _valueB) public onlyAllowed onlyWithTag('Mint') onlyValidFamily(_familyId) {
    
    uint _familySupplyIdx = supply[_familyId]; // get Current Supply for selected family
    uint _tokenId = _getTokenId(_familyId, _familySupplyIdx); 

    // Add to Storage data
    TokenData memory tdata = TokenData(
      _tokenId,
      _familyId,         // <- Following error point here
      _familySupplyIdx,
      _typeId,
      _evoId,  
      _colorId, 
      _traitIds, 
      _specsIds,     
      0,   
      _valueA, 
      _valueB,
      new uint16[](0),
      address(0x0) 
    );

    tokensDatas[_tokenId] = tdata;

    // Finally Mint it
    _safeMint(msg.sender, _tokenId);

    // Once minted, update supply
    _incrementSupply(_familyId);
  }

But I got this error:

CompilerError: Stack too deep, try removing local variables.
   --> contracts/TokenData.sol:120:7:
    |
120 |       _familyId, 
    |       ^^^^^^^^^

In addition, I don't understand why the stack error is referring to an existing local variables. The code isn't creating a new local variable here, so why do this cause an error ??

1 Answer 1

1

The "stack too deep" error in Solidity is caused when the execution stack exceeds its maximum size. This can happen when a contract has too many local variables, or when a function calls itself recursively without a terminating condition.

In your case, the error is likely caused by the large number of arguments in your mint function. Each argument to a function is considered a local variable, and they are all stored on the stack. Since your mint function has 8 arguments, this takes up a significant portion of the stack, leaving little space for other local variables.

One way to solve this problem is to use a struct to group related data together, and pass the struct as a single argument to the mint function. This will reduce the number of arguments and local variables, and should allow the function to be called without exceeding the stack size. Here is an example of how you could modify your code to do this:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

struct TokenData {
    uint16 familyId;
    uint8 typeId;
    uint16 evoId;
    uint32 colorId;
    uint16[] traitIds;
    uint16[4] specsIds;
    uint valueA;
    uint valueB;
}

function mint(TokenData memory tdata) public onlyAllowed onlyWithTag('Mint') onlyValidFamily(tdata.familyId) {
    uint _familyId = tdata.familyId;
    uint _familySupplyIdx = supply[_familyId];
    uint _tokenId = _getTokenId(_familyId, _familySupplyIdx);

    tokensDatas[_tokenId] = tdata;
    _safeMint(msg.sender, _tokenId);
    _incrementSupply(_familyId);
}
4
  • I read a lot about the fact that isn't a good idea to use struct as function argument, and even more when the call is made from external. It's still a valid point ? Or it's now acceptable ?
    – Arthur
    Commented Dec 2, 2022 at 18:28
  • 1
    It is still not recommended to pass large structs as function arguments. This is because all function arguments in Solidity are passed by value, so when a struct is passed as an argument, the entire struct is copied on the stack. For large structs, this can cause a "stack too deep" error, which indicates that the stack memory has run out. To avoid this error, it is recommended to pass structs as reference types by using the memory or storage keyword.
    – VX3
    Commented Dec 2, 2022 at 18:33
  • Okay, so if I understand I can use struct as function argument if I put them in memory.. And I'll never have issue, but there is probably a counter-argument not to abuse this ? I also tried to regroup all uint[8/16/32/256] (not the arrays) into a single uint[] memory _args, and them create the struct by casting every args: new TokenData( uint16(_args[0]), uint8(_args[1]), .... And it's working.. Do you solution is better (More readable, avoid casting) or mine is better (Avoid custom struct) ?
    – Arthur
    Commented Dec 2, 2022 at 18:43
  • 1
    It is generally considered good practice to avoid passing large structs as function arguments, even if you pass them by reference using a pointer. Alternatively, you could consider using a different data structure, such as an array or a map, to store your data. This can make your code more flexible and easier to work with. Ultimately, the best solution will depend on the specific requirements of your program and the context in which it is being used.
    – VX3
    Commented Dec 2, 2022 at 21:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.