24

I know that this has been covered in other questions here but I'm not sure what's happening in my case. My code is returning a compiler error "Stack too deep, try removing local variables." But I only have around five function arguments and a few more local variables. The function in question is given below:

function sell(bool limit, uint256 whichquery, uint8 whichoutcome, uint256 amount, uint16 price) {
        uint cumulativeamountgained;
        uint numorderstobedeleted;
        uint amountnotfilled = amount;
        if(tokenHoldings[msg.sender][whichquery][whichoutcome] < amount) throw;
        if(limit){
            for (uint i=orderbook[whichquery][whichoutcome][0].length-1;i>=0;i--){
                if(orderbook[whichquery][whichoutcome][0][i][0] >= price) {
                    if(orderbook[whichquery][whichoutcome][0][i][1] > amountnotfilled){
                        orderbook[whichquery][whichoutcome][0][i][1] -= amountnotfilled;
                        tokenHoldings[address(orderbook[whichquery][whichoutcome][0][i][2])][whichquery][whichoutcome] += amountnotfilled;

The error is triggered on the last line of this code snippet under the first "whichquery".

9 Answers 9

21

The limit of having not more than 16 local variables could be a problem here. You can try to refactor your code and probably break this function into two parts. That should fix the issue I hope.

Please read the article Solidity stack too deep by James Carlyle to understand more about this issue.

16

You can also scope variables like this

uint var1;
{
    (uint varA, uint varB) = getVars();
    var1 = varA + varB;
}

// now use var1

Inspired by Uniswap.

2
  • 3
    This worked for me. It's a really nice, clean workaround. Aug 24, 2020 at 15:44
  • I still get Stack Too Deep even after doing this
    – dNyrM
    Oct 20, 2023 at 11:22
3

Found nice trick to avoid "Stack too deep" issue:

function swapWithReferral(
    IERC20 fromToken,
    IERC20 destToken,
    uint256 amount,
    uint256 minReturn,
    uint256[] calldata distribution,
    uint256 flags,
    address referral,
    uint256 feePercent
) external makeGasDiscount(flags) returns(uint256 returnAmount) {
    ...
    fromToken.transferFrom(address(this), amount); // <-- Stack too deep
    ...
    destToken.transfer(msg.sender, returnAmount);  // <-- Stack too deep
    ...
}

Just define few additional internal functions like _fromToken() and _destToken() to decode args:

// Helps to avoid "Stack too deep" in swap() method
function _fromToken(bytes memory data) internal pure returns(IERC20 token) {
    assembly {
        token := mload(add(data, 36))
    }
}

// Helps to avoid "Stack too deep" in swap() method
function _destToken(bytes memory data) internal pure returns(IERC20 token) {
    assembly {
        token := mload(add(data, 68))
    }
}

And use these functions instead of access to those vars:

function swapWithReferral(
    IERC20 /*fromToken*/,
    IERC20 /*destToken*/,
    uint256 amount,
    uint256 minReturn,
    uint256[] calldata distribution,
    uint256 flags,
    address referral,
    uint256 feePercent
) external makeGasDiscount(flags) returns(uint256 returnAmount) {
    ...
    _fromToken(msg.sender).transferFrom(address(this), amount); // Works great!
    ...
    _destToken(msg.sender).transfer(msg.sender, returnAmount);  // Works great!
    ...
}

Notice: Be careful with internal and public functions, since msg.data is updated only on external calls. You can externally call public function with this.doSomething().

1

there is limited amount of local variables you are allowed to declare in your function(arguments and return types inclusive), i think it is 16, but as it seems you have exceeded that limit.

7
  • Yip. It's 16 fixed length fields. Strings take two slots, so 8 strings. Jul 7, 2017 at 0:08
  • @RobHitchens seems that strings only take up one slot, which I'm assuming is ptr to its memory location, so you can utilize 15 (minus one stack for operations on said values). Apr 7, 2022 at 1:49
  • Moving from local variable declarations to parameters does reduce the number a bit, but I was able to define a function with 11 memory string arguments, which then sequentially assign all to one local mem variable successfully. The parameters are being passed in and stripped of their calldata position pointers in the process of being moved to a memory location (3 words to 2 words). Have to dig in deeper as to why 11 is the magic number. Apr 8, 2022 at 17:01
  • Confirmed. 11 is the new magic number. You might find pertinent information in either EVM fork history after 2017 or the Solidity compiler release history. Possibly something in the previously experimental ABI V2 that is now part of Solidity proper. Perhaps a kind soul will jump in and update this answer. Apr 9, 2022 at 19:40
  • I'm curious now, so I opened a new question about the magic number. ethereum.stackexchange.com/questions/125846/… Apr 9, 2022 at 19:49
1

When you're dealing with external contracts/interfaces that you can't modify, and their functions return multiple values this may contribute to your Stack too deep error. A solution to this problem is to use custom structs and interfaces to minimize stack depth.

Consider the following StackTooDeep and StackNotTooDeep example contracts which both require getting the result of Uniswap v3's NonfungiblePositionManager.positions(). The positions function returns 12 values and if you have your optimizer disabled then your compilation is likely to fail with Stack too deep as shown in the StackTooDeep example contract.

interface INonfungiblePositionManager {
    function positions(uint256 tokenId) external view returns (
        uint96 nonce,
        address operator,
        address token0,
        address token1,
        uint24 fee,
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity,
        uint256 feeGrowthInside0LastX128,
        uint256 feeGrowthInside1LastX128,
        uint128 tokensOwed0,
        uint128 tokensOwed1
    );
}

contract StackTooDeep {

    INonFungiblePositionManager nftManager;

    constructor(address _nftManager) {
        nftManager = INonFungiblePositionManager(_nftManager);
    }

    function doSomethingWithPosition(uint256 tokenId) external view returns (
        uint96 nonce,
        address operator,
        address token0,
        address token1,
        uint24 fee,
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity,
        uint256 feeGrowthInside0LastX128,
        uint256 feeGrowthInside1LastX128,
        uint128 tokensOwed0,
        uint128 tokensOwed1        
    ) {

        (
            nonce,
            operator,
            token0,
            token1,
            fee,
            tickLower,
            tickUpper,
            liquidity,
            feeGrowthInside0LastX128,
            feeGrowthInside1LastX128,
            tokensOwed0,
            tokensOwed1
        ) = nftManager.positions(tokenId);

    }

}

To mitigate this Stack too deep error, you can define a custom interface like this:

interface INFTManagerPosition {
    // NB: the fields of this custom struct match the order of the primitive types returned by INonFungiblePositionManager.positions()
    struct NFTPosition {
        uint96 nonce;
        address operator;
        address token0;
        address token1;
        uint24 fee;
        int24 tickLower;
        int24 tickUpper;
        uint128 liquidity;
        uint256 feeGrowthInside0LastX128;
        uint256 feeGrowthInside1LastX128;
        uint128 tokensOwed0;
        uint128 tokensOwed1;
    }
    // Note the function is identical to INonFungiblePositionManager.positions except for the return type
    // This workaround does not seem to work if the external function accepts too many input parameters and you want to encode the inputs into a custom struct, likely because this results in a different function selector and so you're calling a function that doesn't exist, however, this is not an issue for the return value since that is not considered when computing the function signature hash
    function positions(uint256 tokenId) external view returns (NFTPosition memory);
}

contract StackNotTooDeep {
    INFTManagerPosition nftManager;

    constructor(address _nftManager) {
        nftManager = INFTManagerPosition(_nftManager);
    }

    function doSomethingWithPosition(uint256 tokenId) external view returns (INFTManagerPosition.NFTPosition memory position) {
        position = nftManager.positions(tokenId);
    }
}
1

If there are too many parameter or local variable in the function , solidity will throw "stack too deep". To resolve the issue, convert the big struct into small struct or use the local variable as part of struct. Below is an example of How I did it.

I need these much parameter to add an item.

struct Item {
    uint256 assetId;
    uint256 assetCategoryId;
    uint256 EstimatedValue;
    uint256 assetValue;
    uint256 minRangeLoanDuration;
    uint256 maxRangeLoanDuration;
    uint256 minRangeInterestRate;
    uint256 maxRangeInterestRate;
    uint256 remainingAmount;
    uint256 paidEmi;
    uint256 EmiAmount;
    uint256 EmiDate;
    string watchBrand;
    string modelName;
    address borrower;
}

So, instead of passing every parameter individually, I made a struct

struct ItemDetails {
    uint256 assetId;
    uint256 assetCategoryId;
    uint256 EstimatedValue;
    uint256 minRangeLoanAmount;
    uint256 maxRangeLoanAmount;
    uint256 minRangeLoanDuration;
    uint256 maxRangeLoanDuration;
    uint256 minRangeInterestRate;
    uint256 maxRangeInterestRate;
    address borrower;
}

and then pass this struct in setProposal function like below

function setProposal(ItemDetails memory itemDetails, string memory _watchBrand, string memory _modelName) external {
    Item memory item = Item(
        itemDetails.assetId,
        itemDetails.assetCategoryId,
        itemDetails.EstimatedValue,
        itemDetails.minRangeLoanAmount,
        itemDetails.maxRangeLoanAmount,
        itemDetails.minRangeLoanDuration,
        itemDetails.maxRangeLoanDuration,
        itemDetails.minRangeInterestRate,
        itemDetails.maxRangeInterestRate,
        itemDetails.EstimatedValue,
        0,
        0,
        0,
        _watchBrand,
        _modelName,
        itemDetails.borrower
    );
}

I hope it helps!

0

Good solution is break down function, and make a relation with a specfic value.

My first solution was:

 struct Statement{
        uint256 id;
        uint256 userId;
        uint256 surveyAreaId;
        string latitude;
        string longitude;
        string indicator1;
        string indicator2;
        string indicator3;
        string indicator4;
        string indicator5;
    }

And I tried to all varible inside one function and compiler showed me ""Stack too deep, try removing local variables.""

So I broke down all code like that:

struct UINTStatement{
        uint256 id;
        uint256 userId;
        uint256 surveyAreaId;
    }
    
    mapping(uint256=>UINTStatement) public uintStatements;
    
    struct StringStatement{
        uint256 id;
        string latitude;
        string longitude;
        string indicator1;
        string indicator2;
        string indicator3;
        string indicator4;
        string indicator5;
    }
    
    mapping(uint256=>StringStatement) public stringStatements;

Make different two function to create value.

0

My personal solution for this issue was to select the "Enable Truffle Compilation" option in Remix: https://remix-ide.readthedocs.io/en/latest/truffle.html (I already had Rremix connected to my local files with remixd which is a prerequisite). Somehow my local compiler could resolve it but not the standard browser one.

0

If your function cross 8 parameter it will cause you this error.

Make sure your function have less than 8 parameter it will work.

1
  • Naah, one of my functions threw this error while having 6 parameters, because all of them were arrays. doesn't really define a certain number of limits. Sep 20, 2023 at 7:19

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.