foundry-rs / foundry

Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
https://getfoundry.sh
Apache License 2.0
8.29k stars 1.75k forks source link

"Stack too deep" error when compiling #832

Closed thetechnocratic closed 2 years ago

thetechnocratic commented 2 years ago

Component

Forge

Have you ensured that all of these are up to date?

What version of Foundry are you on?

forge 0.1.0 (d08a59e 2022-03-01T00:29:11.959377+00:00)

What command(s) is the bug in?

forge build

Operating System

macOS (amd)

Describe the bug

When attempting to compile, forge throws the following error:

Compiling...
Compiling 1 files with 0.8.10
Compiling 26 files with 0.8.11
Compilation finished successfully
Compilation finished successfully
Error: 
   0: Compiler run failed
      CompilerError: Stack too deep when compiling inline assembly: Variable value0 is 3 slot(s) too deep inside the stack.

   0: 

Location:
   cli/src/cmd/mod.rs:87

  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   1: _main<unknown>
      at <unknown source file>:<unknown line>
   2: _main<unknown>
      at <unknown source file>:<unknown line>
   3: _main<unknown>
      at <unknown source file>:<unknown line>
   4: _main<unknown>
      at <unknown source file>:<unknown line>
   5: __mh_execute_header<unknown>
      at <unknown source file>:<unknown line>
   6: __mh_execute_header<unknown>
      at <unknown source file>:<unknown line>
   7: __mh_execute_header<unknown>
      at <unknown source file>:<unknown line>
   8: __mh_execute_header<unknown>
      at <unknown source file>:<unknown line>
   9: _main<unknown>
      at <unknown source file>:<unknown line>

Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
thetechnocratic:~/code/defi/os/alchemix/gelato-keepers/gelato-keepers$ forge --v

I have narrowed it down to a function that builds and saves a struct to a mapping. The struct is of the format:

    struct MyStruct {
        uint8 a;
        address b;
        address c;
        uint256 d;
        uint256 e;
        uint256 f;
        uint256 g;
        uint256 h;
        uint256 i;
        uint256 j;
        uint256 k;
        uint256 l;
        uint256 m;
        uint256 n;
        bool o;
    }

When i set the optimizer to 1 or more runs, the error names 2 slots instead of 3:

Compiling...
Compiling 1 files with 0.8.10
Compiling 26 files with 0.8.11
Compilation finished successfully
Compilation finished successfully
Error: 
   0: Compiler run failed
      CompilerError: Stack too deep when compiling inline assembly: Variable value0 is 2 slot(s) too deep inside the stack.

   0: 

Location:
   cli/src/cmd/mod.rs:87

  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   1: _main<unknown>
      at <unknown source file>:<unknown line>
   2: _main<unknown>
      at <unknown source file>:<unknown line>
   3: _main<unknown>
      at <unknown source file>:<unknown line>
   4: _main<unknown>
      at <unknown source file>:<unknown line>
   5: __mh_execute_header<unknown>
      at <unknown source file>:<unknown line>
   6: __mh_execute_header<unknown>
      at <unknown source file>:<unknown line>
   7: __mh_execute_header<unknown>
      at <unknown source file>:<unknown line>
   8: __mh_execute_header<unknown>
      at <unknown source file>:<unknown line>
   9: _main<unknown>
      at <unknown source file>:<unknown line>

Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.

similar (and actually more complex / deeper stack) code compiles fine when i use hardhat.

gakonst commented 2 years ago

Could you please share the forge command you ran, as well as any foundry.toml in your repo?

Same question on Hardhat, what does your hardhat.config.js look like?

thetechnocratic commented 2 years ago

export RUST_BACKTRACE=full; forge build

foundry.toml:

[default]
src = 'src'
out = 'out'
libs = ['lib']
remappings = ['ds-test/=lib/ds-test/src/']
optimizer = false
optimizer_runs = 0
evm_version = 'london'

hardhat.config.ts:


{
  solidity: {
    version: "0.8.11",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
  contractSizer: {
    alphaSort: true,
    disambiguatePaths: false,
    runOnCompile: false,
  },
  networks: {
    coverage: {
      url: "http://localhost:8555",
      gas: 15000000000,
      gasPrice: 80000000000,
      baseFee: 1,
    },
    mainnet: {
      chainId: 1,
      hardfork: "london",
      gasPrice: 100000000000,
      url: `https://eth-mainnet.alchemyapi.io/v2/${process.env["ALCHEMY_API_KEY"]}`,
      timeout: 60000000,
      accounts: [process.env.MAINNET_DEPLOYER_PK]
    },
    hardhat: {
      chainId: 1337,
      allowUnlimitedContractSize: false,
      hardfork: "london",
      accounts: [
        {
          privateKey: process.env.MAINNET_DEPLOYER_PK || generateRandomHex(64),
          balance: "1000000000000000000000000",
        },
        {
          generateRandomHex(64),
          balance: "2000000000000000000000000",
        },
        {
          generateRandomHex(64),
          balance: "3000000000000000000000000",
        },
      ],
    },
  },
  gasReporter: {
    enabled: process.env.REPORT_GAS !== undefined,
    coinmarketcap: process.env.COINMARKETCAP_API_KEY || "",
    currency: "USD",
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY,
  },
};```

I can change the hardhat optimizer settings to whatever i want and it still works.
When i change the optimizer settings in the foundry.toml, the only thing that's different is the error calls out 2 slots instead of 3.
mattsse commented 2 years ago

I can change the hardhat optimizer settings to whatever i want and it still works. When i change the optimizer settings in the foundry.toml, the only thing that's different is the error calls out 2 slots instead of 3.

both enabled and runs?

thetechnocratic commented 2 years ago

I can change the hardhat optimizer settings to whatever i want and it still works. When i change the optimizer settings in the foundry.toml, the only thing that's different is the error calls out 2 slots instead of 3.

both enabled and runs?

When the optimizer is enabled and runs > 0, the error calls out 2 slots instead of 3.

0xfoobar commented 2 years ago

Here's a complete reproducible example @onbjerg @gakonst

pragma solidity ^0.8.11;

contract StackTooDeep {
    struct MyStruct {
        uint8 a;
        address b;
        address c;
        uint256 d;
        uint256 e;
        uint256 f;
        uint256 g;
        uint256 h;
        uint256 i;
        uint256 j;
        uint256 k;
        uint256 l;
        uint256 m;
        uint256 n;
        bool o;
    }

    mapping(uint => MyStruct) public map;
}

This fails on compilation with forge build with the error technocratic mentioned above. Interestingly enough, removing the mapping map (or just setting its visibility to internal/private) lets the compilation succeed, even though the map is not used anywhere. Any clue what's going on?

deomaius commented 2 years ago

I was also facing this error - try compressing similar typed params into arrays and you should be able to resolve it, the error is accurate to the restraints of the compilation process using rust it seems.

0xfoobar commented 2 years ago

Appreciate the feedback, but this is an EVM compatibility error and our equivalence concerns mean we can't change the underlying code. If it comes with Hardhat, it should compile with Foundry. So trying to figure out the best way to fix the underlying compiler so it compiles valid Solidity code properly.

mattsse commented 2 years ago

this also fails in remix, can have a look at hardhat's settings. Refactoring this structure is a good call though :)

onbjerg commented 2 years ago

I was also facing this error - try compressing similar typed params into arrays and you should be able to resolve it, the error is accurate to the restraints of the compilation process using rust it seems.

This is not a Rust issue, the error is directly from solc

pragma solidity ^0.8.11;

contract StackTooDeep {
    struct MyStruct {
        // ..
    }

    mapping(uint => MyStruct) public map;
}

This fails on compilation with forge build with the error technocratic mentioned above. Interestingly enough, removing the mapping map (or just setting its visibility to internal/private) lets the compilation succeed, even though the map is not used anywhere. Any clue what's going on?

The map is used because Solidity will generate getter functions for it unless it is internal/private 😄

My best guess is that it is some flag we set that Hardhat doesn't. I checked the Solidity issue tracker and most of the "stack to deep" issues are related to their new codegen backend, but I don't think that's enabled by default?

thetechnocratic commented 2 years ago

After more time looking at this I don't think the failure is unique to foundry, so I'm going to close this issue.

As @0xfoobar mentioned, setting the map to internal/private fixes the compilation error, which is sufficient for what we need. That means that there is likely something in the auto-generated getter that is causing the "stack too deep" error.

gakonst commented 2 years ago

sg! thanks! if you think there's some foundry-specific issue that causes extra stack slots to be allocated for whatever reason, please let us know