43

When trying to compile, I am getting the following error:

"Internal compiler error: Stack too deep, try removing local variables."

Is there any way around this? I'm not sure if I'll be able to remove enough variables to fix this.

Thanks!

8 Answers 8

32

You're hitting a StackTooDeepException.

The Solidity code doesn't appear to be consistent in the number of variables it sees as a problem, but you've got a limit of around 16 or 17. (Though clearly the lower limit of 16 will be the one that kicks in...)

CommonSubexpressionEliminator.cpp and CompilerUtils.cpp:

assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep, try removing local variables.");

ContractCompiler.cpp:

solAssert(stackLayout.size() <= 17, "Stack too deep, try removing local variables.");

Without seeing your code it's difficult to comment further on possible solutions, but one thing to try would be to split bigger functions into smaller ones.


Edit 2019:

A very detailed explanation of this error, and how it can be avoided, is discussed in the article “Stack Too Deep”- Error in Solidity.

7
  • Thanks for the suggestion! Would an array count as 1 local variable?
    – Amer Ameen
    Commented Jun 15, 2016 at 21:08
  • 3
    I believe so, yes. Otherwise array sizes would also be limited to 16, which isn't a particularly large array. Commented Jun 16, 2016 at 8:05
  • I am not trying to revive this, but i had to ask since i am getting this error too. is the maximum number of local variables less than 10 now ? I am having this error with 15 variables Commented Feb 20, 2018 at 11:19
  • Hi @MedMansour - spotted your other question about this. How many do you have to remove before the error disappears? Commented Feb 20, 2018 at 18:45
  • I did get no error when leaving about 8 or 9 variables from my struct, but i already switched to passing a fixed size array instead or sperate arguments. yet i don't know what does local variable means. is it only the variables in arguments ? or the ones in returns() statement or the sum of those with the local variables inside the function. Commented Feb 22, 2018 at 7:36
11

Uniswap seems to have found a neat solution for this issue. Surround a part of your function with brackets:

{ // scope for _token{0,1}, avoids stack too deep errors
  address _token0 = token0;
  address _token1 = token1;
  require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
  if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
  if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
  if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
  balance0 = IERC20(_token0).balanceOf(address(this));
  balance1 = IERC20(_token1).balanceOf(address(this));
}

Take a look at UniswapV2.sol for full context.

3
  • What does that change, do you think?
    – Lee
    Commented Aug 5, 2020 at 19:47
  • Not sure .. I saw it in the production code of Uniswap v2 so I thought that it must be good. Commented Aug 5, 2020 at 20:36
  • 6
    The stack problem comes up most commonly when there are too many local variables/parameters in the current scope. If you put some variable declarations in a block, they go out of scope when the block ends, decreasing the number of variables that have to be kept on the stack after it. Ultimately though, tracking the lifetime of local variables in a function is something that the compiler/optimizer should be able to figure out without such "tricks" so even if this still helps with the current code generator, the upcoming Yul-based codegen should be much smarter about it.
    – cameel
    Commented Nov 21, 2021 at 15:11
6

A workaround for this is to put local variables into an array of the same type as on the EVM arrays only occupy one stack slot. Thereby, functions can get much bigger before hitting the limit of 16 slots.

For example:

contract A {

    // This will get the error: 'stack too deep, try...'
    function deepStack
    (
        uint8 _a,
        uint8 _b,
        uint8 _c,
        uint8 _d,
        uint16 _e,
        uint16 _f,
        uint16 _g,
        uint16 _h,
        uint32 _i,
        uint32 _j,
        uint32 _k,
        uint32 _l,
        uint64 _m,
        uint64 _n,
        uint64 _o,
        uint64 _p,
        uint128 _q
    )
        public
        returns (bool success)
    {
        return true;
    }

    // This function works
    function deepStackSolution
    (
        uint8[] _aToD,
        uint16[] _eToH,
        uint32[] _iToL,
        uint64[] _mToP,
        uint128 _q
    )
        public
        returns (bool success)
    {
        return true;
    }

}

However, keep in mind that the storage capability of each slot is still limited. This way the provided storage will just be used more efficiently. If you are using really large numbers this might soon reach it's limits as well.

4

https://github.com/ethereum/solidity/issues/267

It depends on how complex the expressions inside the function are, but more than 16 local variables will not work. This story should fix it, though: https://www.pivotaltracker.com/n/projects/1189488/stories/99085498

The story has not been started.

3
  • Seems that it is completed now.
    – updogliu
    Commented Dec 12, 2017 at 17:54
  • @updogliu or whoever knows the latest news, please do post an answer.
    – eth
    Commented Dec 30, 2017 at 13:00
  • I tend to agree with @eth. This doesn't seem to be resolved on 0.4.18. Commented Jan 12, 2018 at 10:13
1

Here's a funny thing. I simply changed a method from public to external and got the message:

/Users/cliff/Documents/in-app-pro-shop/contracts/SKUFactory.sol:42:23: 
CompilerError: Stack too deep, try removing local variables.
skus.push(SKU(_shopId, skuId, _skuTypeId, _price, _name, _desc, _consumable, _limited, _limit));
              ^-----^

Changing it back to public removes the error! Apparently, it's a little more nuanced than just how many local variables there are. Here's the function:

/**
 * @notice Create a SKU (Shopkeeping Unit) for a Shop
 * @dev Can only be run by shop owner
 */
function createSKU(
    uint256 _shopId,
    uint256 _skuTypeId,
    uint256 _price,
    string _name,
    string _desc,
    bool _consumable,
    bool _limited,
    uint256 _limit
)
    public
    onlyShopOwner(_shopId)
    returns(uint256)
{
    // SKUs must have a non-zero price
    require(_price > 0);

    // Get SKU ID
    uint256 skuId = skus.length;

    // Create and store SKU Type
    skus.push(SKU(_shopId, skuId, _skuTypeId, _price, _name, _desc, _consumable, _limited, _limit));

    // Add SKU to Shop's SKU list
    shopSKUs[_shopId].push(skuId);

    // Add SKU ID to SKU Type's SKU list
    skuTypeSKUs[_skuTypeId].push(skuId);

    // Emit Event with name of the new SKU
    emit NewSKU(_shopId, skuId, _name);

    // Return the new SKU ID
    return skuId;
}

I was making the change in response to this discussion about best practices for external vs public, where it is explained (albeit, sort of murkily) how functions are handled differently in those cases. I'm guessing that's at the root of why there isn't a specific number of local variables that triggers this error.

1
  • 1
    The actual root cause is that it's about stack slots and variables do not necessarily map to slots 1:1. I think that the reason you got the error is that calldata string parameters require two slots while memory strings take only one. Choosing external rather than public probably forced the parameters to be in calldata.
    – cameel
    Commented Nov 21, 2021 at 16:04
1

The cleanest way to solve the Stack Too Deep issue is to use structs for your function params and variables:

contract Contract {
    struct Params {
        address param1;
        uint256 param2;
    }
    struct Vars {
        uint256 var1;
        uint256 var2;
        uint256 var3;
        uint256 var4;
    }

    function noStackIssues(Params calldata params) external pure {
        params.param1;
        params.param2;
        Vars memory vars;
        vars.var1 = 1;
        vars.var2 = 2;
        vars.var3 = 3;
        vars.var4 = 4;
    }
}

What this does under the hood is make boatloads of MLOAD and MSTORE calls, so you should meter you gas usage since it might increase a bit.

0

Just pack your variables into a memory array. For example, say you have 30 local addresses variables. Instead of:

address addr1 = 0x0000000000000000000000000000000000000001;
address addr2 = 0x0000000000000000000000000000000000000002;
address addr3 = 0x0000000000000000000000000000000000000003;
// etc

You can do:

address[] memory joinedAddresses = new address[](30);
joinedAddresses[0] = 0x0000000000000000000000000000000000000001;
joinedAddresses[1] = 0x0000000000000000000000000000000000000002;
joinedAddresses[2] = 0x0000000000000000000000000000000000000003;
// etc

Note: I tested this method in Solidity 0.8.4.

0

Quick Fix (Temporary) -

If you're using Foundry, then try adding viaIR = true in foundry.toml file. (As shown on line 5)

Compilation might take more time, but you would be able to get the desired results.

enter image description here

For me, solc took around 50 seconds to compile the files.

enter image description here

If you are using Hardhat, this property should be added in the hardhat.config.js in JSON format.

PS: It is suggested to avoid adding this configuration, if you can add more scopes or split the larger functions into smaller ones to limit the variable's lifespan.

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.