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!
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.");
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.
returns()
statement or the sum of those with the local variables inside the function.
Commented
Feb 22, 2018 at 7:36
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.
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.
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.
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.
external
rather than public
probably forced the parameters to be in calldata.
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.
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.
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.
For me, solc took around 50 seconds to compile the files.
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.