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);
}
}