eth-brownie / brownie

A Python-based development and testing framework for smart contracts targeting the Ethereum Virtual Machine.
https://eth-brownie.readthedocs.io
MIT License
2.64k stars 550 forks source link

Brownie compile can't find relative imports #1367

Open josh-davis-vicinft opened 2 years ago

josh-davis-vicinft commented 2 years ago

Environment information

Brownie stopped being able to compile solidity files that use relative imports to refer to files in the local directory tree. I can reproduce it with this simple test case. This was working for me earlier, and I don't know why it's not working any more.

I run brownie init in a clean folder, create ContractA.sol and ContractB.sol in ./contracts, and run brownie compile.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

contract ContractA {

}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import "./ContractA.sol";

contract ContractB is ContractA {

}
$ brownie compile
Brownie v1.17.1 - Python development framework for Ethereum

Compiling contracts...
  Solc version: 0.8.10
  Optimizer: Enabled  Runs: 200
  EVM Version: Istanbul
CompilerError: solc returned the following errors:

ParserError: Source "/home/my_username/.brownie/packages/contracts/ContractA.sol" not found: File not found.
 --> contracts/ContractB.sol:4:1:
  |
4 | import "./ContractA.sol";
  | ^^^^^^^^^^^^^^^^^^^^^^^^^

Why is it looking for the file in /home/my_username/.brownie/packages/contracts/ContractA.sol instead of looking for it right there in the same directory?

I can get a stack trace if I call it from python:

from brownie import network, project
network.connect("development")
test_project = project.load(".")
Compiling contracts...
  Solc version: 0.8.10
  Optimizer: Enabled  Runs: 200
  EVM Version: Istanbul
---------------------------------------------------------------------------
SolcError                                 Traceback (most recent call last)
~/pyenv/eth/lib/python3.8/site-packages/brownie/project/compiler/solidity.py in compile_from_input_json(input_json, silent, allow_paths)
     77     try:
---> 78         return solcx.compile_standard(input_json, allow_paths=allow_paths)
     79     except solcx.exceptions.SolcError as e:

~/pyenv/eth/lib/python3.8/site-packages/solcx/main.py in compile_standard(input_data, base_path, allow_paths, output_dir, overwrite, solc_binary, solc_version, allow_empty)
    393             )
--> 394             raise SolcError(
    395                 error_message,

SolcError: ParserError: Source "/home/my_username/.brownie/packages/contracts/ContractA.sol" not found: File not found.
 --> contracts/ContractB.sol:4:1:
  |
4 | import "./ContractA.sol";
  | ^^^^^^^^^^^^^^^^^^^^^^^^^

> command: `/home/my_username/.solcx/solc-v0.8.10 --standard-json --allow-paths /home/my_username/test_case,/home/my_username/.brownie/packages`
> return code: `0`
> stdout:
{"errors":[{"component":"general","errorCode":"6275","formattedMessage":"ParserError: Source \"/home/my_username/.brownie/packages/contracts/ContractA.sol\" not found: File not found.\n --> contracts/ContractB.sol:4:1:\n  |\n4 | import \"./ContractA.sol\";\n  | ^^^^^^^^^^^^^^^^^^^^^^^^^\n\n","message":"Source \"/home/my_username/.brownie/packages/contracts/ContractA.sol\" not found: File not found.","severity":"error","sourceLocation":{"end":82,"file":"contracts/ContractB.sol","start":57},"type":"ParserError"}],"sources":{}}

> stderr:

During handling of the above exception, another exception occurred:

CompilerError                             Traceback (most recent call last)
<ipython-input-4-086384e98c33> in <module>
----> 1 test_project = project.load(".")

~/pyenv/eth/lib/python3.8/site-packages/brownie/project/main.py in load(project_path, name)
    749 
    750     # load sources and build
--> 751     return Project(name, project_path)
    752 
    753 

~/pyenv/eth/lib/python3.8/site-packages/brownie/project/main.py in __init__(self, name, project_path)
    181         self._name = name
    182         self._active = False
--> 183         self.load()
    184 
    185     def load(self) -> None:

~/pyenv/eth/lib/python3.8/site-packages/brownie/project/main.py in load(self)
    236         # compile updated sources, update build
    237         changed = self._get_changed_contracts(interface_hashes)
--> 238         self._compile(changed, self._compiler_config, False)
    239         self._compile_interfaces(interface_hashes)
    240         self._load_dependency_artifacts()

~/pyenv/eth/lib/python3.8/site-packages/brownie/project/main.py in _compile(self, contract_sources, compiler_config, silent)
     93 
     94         try:
---> 95             build_json = compiler.compile_and_format(
     96                 contract_sources,
     97                 solc_version=compiler_config["solc"].get("version", None),

~/pyenv/eth/lib/python3.8/site-packages/brownie/project/compiler/__init__.py in compile_and_format(contract_sources, solc_version, vyper_version, optimize, runs, evm_version, silent, allow_paths, interface_sources, remappings, optimizer)
    139         )
    140 
--> 141         output_json = compile_from_input_json(input_json, silent, allow_paths)
    142         build_json.update(generate_build_json(input_json, output_json, compiler_data, silent))
    143 

~/pyenv/eth/lib/python3.8/site-packages/brownie/project/compiler/__init__.py in compile_from_input_json(input_json, silent, allow_paths)
    254     if input_json["language"] == "Solidity":
    255         allow_paths = _get_allow_paths(allow_paths, input_json["settings"]["remappings"])
--> 256         return solidity.compile_from_input_json(input_json, silent, allow_paths)
    257 
    258     raise UnsupportedLanguage(f"{input_json['language']}")

~/pyenv/eth/lib/python3.8/site-packages/brownie/project/compiler/solidity.py in compile_from_input_json(input_json, silent, allow_paths)
     78         return solcx.compile_standard(input_json, allow_paths=allow_paths)
     79     except solcx.exceptions.SolcError as e:
---> 80         raise CompilerError(e, "solc")
     81 
     82 

CompilerError: solc returned the following errors:

ParserError: Source "/home/my_username/.brownie/packages/contracts/ContractA.sol" not found: File not found.
 --> contracts/ContractB.sol:4:1:
  |
4 | import "./ContractA.sol";
  | ^^^^^^^^^^^^^^^^^^^^^^^^^
josh-davis-vicinft commented 2 years ago

So this is weird. I just ran brownie console and it compiled just fine. Now that I exited out of that, running brownie compile and brownie compile --all work.

I don't understand it, but that seems like it might be a workaround if anyone else ever finds themselves in this state.

josh-davis-vicinft commented 2 years ago

I'm in this state again, and entering brownie console doesn't help. Every brownie project I have is affected, starting a new command shell doesn't help, and reinstalling brownie doesn't help.

Edit: I even tried rebooting my computer and it's still broken.

Does anyone have any ideas?

r4VP4 commented 2 years ago

I'm having the same problem. Everything works fine and at some point brownie starts to look in /home/username/.brownie/packages/ and relative imports stop working

mpeyfuss commented 2 years ago

What Brownie version was working for relative imports? And what solc version? Or what has changed on your end since relative imports were working?

There are many packages being used by Brownie, in addition to the solc compiler that could affect this.

Basically, there has been a change somewhere that has cause this, but it is hard to pinpoint without the above information. It is relatively easy to rule out the solc compiler if you try multiple versions, like 0.8.9 or earlier.

Temporary solution is to use absolute paths. Annoying for sure but at least it should work!

josh-davis-vicinft commented 2 years ago

It worked with Brownie version v1.17.1 and solc version 0.8.10+commit.fc410830.Linux.g++, then stopped working with those same versions, then started working again with those same versions, then stopped working again with those same versions, all within about 24 hours. I didn't install, uninstall, or upgrade anything during this time.

My workaround was to put a symlink to my project's contracts folder in /home/username/.brownie/packages.

mpeyfuss commented 2 years ago

I think this may have to do with how Brownie is generating the Standard JSON input for solc but not 100% sure. Check under build/contracts for the json output from compilation (hopefully something was output). You should then be able to find the field called allSourcePaths. See if the source path for ContractA is being set properly. The field named nodes should also give you this information.

If nothing is output from the failed compilation attempts, then we'll need to find a way to get some of those variables created during compilation.

This will hopefully help nail down the root of the issue.

josh-davis-vicinft commented 2 years ago

It doesn't generate the JSON files. Looks like it crashes before it gets to that point.

I tried changing the pragma line from 0.8.10 to 0.8.11 in both contracts. It downloaded the solc-linux-amd64-v0.8.11+commit.d7f03943 compiler, but I still get the same error.

mpeyfuss commented 2 years ago

Interestingly enough, I'm using solc v0.8.9 and got relative paths to compile. Can you try that version and see if it works?

josh-davis-vicinft commented 2 years ago

Same error for me with 0.8.9, and this has worked for me in the past with 0.8.10:

Brownie v1.17.1 - Python development framework for Ethereum

Downloading from https://solc-bin.ethereum.org/linux-amd64/solc-linux-amd64-v0.8.9+commit.e5eed63a
100%|██████| 12.4M/12.4M [00:27<00:00, 442kiB/s]
solc 0.8.9 successfully installed at: /home/jdavis/.solcx/solc-v0.8.9
Compiling contracts...
  Solc version: 0.8.9
  Optimizer: Enabled  Runs: 200
  EVM Version: Istanbul
CompilerError: solc returned the following errors:

ParserError: Source "/home/jdavis/.brownie/packages/contracts/ContractA.sol" not found: File not found.
 --> contracts/ContractB.sol:4:1:
  |
4 | import "./ContractA.sol";
  | ^^^^^^^^^^^^^^^^^^^^^^^^^
josh-davis-vicinft commented 2 years ago

Just following up to report that this error still exists in Brownie v1.18.1

mpeyfuss commented 2 years ago

One thing I realized I never asked and can’t tell from your original issue… are you running the cli command from the brownie project folder or from within a sub folder of the project? solc can only perform sub folder relative path compilation.

josh-davis-vicinft commented 2 years ago

Directly from the brownie project folder.

test_case$ brownie compile
Brownie v1.18.1 - Python development framework for Ethereum

Compiling contracts...
  Solc version: 0.8.11
  Optimizer: Enabled  Runs: 200
  EVM Version: Istanbul
CompilerError: solc returned the following errors:

ParserError: Source "/home/jdavis/.brownie/packages/contracts/ContractA.sol" not found: File not found.
 --> contracts/ContractB.sol:4:1:
  |
4 | import "./ContractA.sol";
  | ^^^^^^^^^^^^^^^^^^^^^^^^^
test_case$ ls -R
.:
build  contracts  interfaces  reports  scripts  tests

./build:
contracts  deployments  interfaces

./build/contracts:

./build/deployments:

./build/interfaces:

./contracts:
ContractA.sol  ContractB.sol

./interfaces:

./reports:

./scripts:

./tests:
josh-davis-vicinft commented 2 years ago

I found another workaround. If I set the fully-qualified path to my contracts folder as a remapping, then it doesn't look for the contracts in DATA_FOLDER.

compiler:
  solc:
    remappings:
      - "contracts=/fully/qualified/path/to/contracts"

This seems to be required even though I'm compiling directly from the project folder.

cameel commented 2 years ago

Just a heads up: paths and remappings become a part of contract metadata. If you use an absolute path and publish your metadata (and it's recommended to publish it, see https://sourcify.dev), you might end up exposing your local info - e.g. your username via home dir path. This info can be tied directly to the contract via the metadata hash embedded in the bytecode.

josh-davis-vicinft commented 1 year ago

So I figured out the root cause.

I don't remember when or how, but somehow a folder named contracts was created under $HOME/.brownie/packages. Deleting this folder fixed the problem.

I'm leaving this open because I don't believe the presence of a folder named contracts under $HOME/.brownie/packages should break importing files from relative paths.