При попытке скомпилировать я получаю следующую ошибку:
«Внутренняя ошибка компилятора: слишком глубокий стек, попробуйте удалить локальные переменные».
Есть ли способ обойти это? Я не уверен, смогу ли я удалить достаточно переменных, чтобы это исправить.
Спасибо!
Вы сталкиваетесь с StackTooDeepException .
Код Solidity не кажется последовательным в количестве переменных, которые он считает проблемой, но у вас есть предел около 16 или 17. (Хотя ясно, что нижний предел 16 будет тем, который сработает. ..)
CommonSubexpressionEliminator.cpp и CompilerUtils.cpp :
assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep, try removing local variables.");
solAssert(stackLayout.size() <= 17, "Stack too deep, try removing local variables.");
Не видя вашего кода, трудно комментировать возможные решения, но стоит попробовать разделить большие функции на более мелкие.
Изменить 2019:
Очень подробное объяснение этой ошибки, и как ее можно избежать, рассмотрено в статье «Stack Too Deep» — Ошибка в Solidity .
Uniswap, похоже, нашел изящное решение этой проблемы. Окружите часть вашей функции скобками:
{ // 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));
}
Взгляните на UniswapV2.sol для полного контекста.
https://github.com/ethereum/solidity/issues/267
Это зависит от того, насколько сложны выражения внутри функции, но больше 16 локальных переменных работать не будет. Эта история должна это исправить: https://www.pivotaltracker.com/n/projects/1189488/stories/99085498
История не начата.
Обходной путь для этого состоит в том, чтобы поместить локальные переменные в массив того же типа, что и в массивах EVM, занимающих только один слот стека. Таким образом, функции могут стать намного больше, прежде чем будет достигнуто ограничение в 16 слотов.
Например:
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;
}
}
Однако имейте в виду, что возможности хранения каждого слота по-прежнему ограничены. Таким образом, предоставленное хранилище будет использоваться более эффективно. Если вы используете действительно большие числа, это также может вскоре достичь своих пределов.
Вот забавная вещь. Я просто изменил метод с public
на external
и получил сообщение:
/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));
^-----^
Изменение его обратно на public
удаляет ошибку! По-видимому, это немного больше нюансов, чем просто количество локальных переменных. Вот функция:
/**
* @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;
}
Я внес изменение в ответ на это обсуждение лучших практик для external
vs public
, где объясняется (хотя и довольно туманно), как функции обрабатываются по-разному в этих случаях. Я предполагаю, что это корень того, почему нет определенного количества локальных переменных, которые вызывают эту ошибку.
external
, а не public
принудительное использование параметров в calldata.Просто упакуйте свои переменные в массив памяти. Например, предположим, что у вас есть 30 переменных локальных адресов. Вместо:
address addr1 = 0x0000000000000000000000000000000000000001;
address addr2 = 0x0000000000000000000000000000000000000002;
address addr3 = 0x0000000000000000000000000000000000000003;
// etc
Ты можешь сделать:
address[] memory joinedAddresses = new address[](30);
joinedAddresses[0] = 0x0000000000000000000000000000000000000001;
joinedAddresses[1] = 0x0000000000000000000000000000000000000002;
joinedAddresses[2] = 0x0000000000000000000000000000000000000003;
// etc
Примечание. Я тестировал этот метод в Solidity 0.8.4.
Амер Амин
Ричард Хоррокс
Каки Мастер Времени
Ричард Хоррокс
Каки Мастер Времени
returns()
инструкции, или сумма переменных с локальными переменными внутри функции.sunwarr10r
return ( myStruct[id].var1, myStruct[id].var2, ...);
верблюд
makeStackItems()
в исходном коде компилятора). Также некоторые дополнительные слоты могут использоваться внутри — язык намеренно скрывает стек, и точное расположение стека следует рассматривать как деталь реализации.