def get_interface_codes(root_path: Path, contract_sources: ContractCodes) -> Dict: interface_codes: Dict = {} interfaces: Dict = {} for file_path, code in contract_sources.items(): interfaces[file_path] = {} parent_path = root_path.joinpath(file_path).parent interface_codes = extract_file_interface_imports(code) for interface_name, interface_path in interface_codes.items(): base_paths = [parent_path] if not interface_path.startswith(".") and root_path.joinpath(file_path).exists(): base_paths.append(root_path) elif interface_path.startswith("../") and len(Path(file_path).parent.parts) < Path( interface_path ).parts.count(".."): raise FileNotFoundError( f"{file_path} - Cannot perform relative import outside of base folder" ) valid_path = get_interface_file_path(base_paths, interface_path) with valid_path.open() as fh: code = fh.read() if valid_path.suffix == ".json": interfaces[file_path][interface_name] = { "type": "json", "code": json.loads(code.encode()), } else: interfaces[file_path][interface_name] = {"type": "vyper", "code": code} return interfaces
def get_interface_codes( root_path: Union[Path, None], contract_path: ContractPath, contract_sources: ContractCodes, interface_sources: Dict, ) -> Dict: interface_codes: Dict = {} interfaces: Dict = {} code = contract_sources[contract_path] interface_codes = extract_file_interface_imports(code) for interface_name, interface_path in interface_codes.items(): path = Path(contract_path).parent.joinpath(interface_path).as_posix() keys = [_standardize_path(path)] if not interface_path.startswith("."): keys.append(interface_path) key = next((i for i in keys if i in interface_sources), None) if key: interfaces[interface_name] = interface_sources[key] continue key = next((i + ".vy" for i in keys if i + ".vy" in contract_sources), None) if key: interfaces[interface_name] = { "type": "vyper", "code": contract_sources[key] } continue if root_path is None: raise FileNotFoundError( f"Cannot locate interface '{interface_path}{{.vy,.json}}'") parent_path = root_path.joinpath(contract_path).parent base_paths = [parent_path] if not interface_path.startswith("."): base_paths.append(root_path) elif interface_path.startswith("../") and len( Path(contract_path).parent.parts) < Path( interface_path).parts.count(".."): raise FileNotFoundError( f"{contract_path} - Cannot perform relative import outside of base folder" ) valid_path = get_interface_file_path(base_paths, interface_path) with valid_path.open() as fh: code = fh.read() if valid_path.suffix == ".json": interfaces[interface_name] = { "type": "json", "code": json.loads(code.encode()) } else: interfaces[interface_name] = {"type": "vyper", "code": code} return interfaces
def get_interface_codes(root_path: Path, contract_sources: ContractCodes) -> Dict: interface_codes: Dict = {} interfaces: Dict = {} for file_path, code in contract_sources.items(): interfaces[file_path] = {} parent_path = root_path.joinpath(file_path).parent interface_codes = extract_file_interface_imports(code) for interface_name, interface_path in interface_codes.items(): base_paths = [parent_path] if not interface_path.startswith(".") and root_path.joinpath(file_path).exists(): base_paths.append(root_path) elif interface_path.startswith("../") and len(Path(file_path).parent.parts) < Path( interface_path ).parts.count(".."): raise FileNotFoundError( f"{file_path} - Cannot perform relative import outside of base folder" ) valid_path = get_interface_file_path(base_paths, interface_path) with valid_path.open() as fh: code = fh.read() if valid_path.suffix == ".json": contents = json.loads(code.encode()) # EthPM Manifest (EIP-2678) if "contractTypes" in contents: if ( interface_name not in contents["contractTypes"] or "abi" not in contents["contractTypes"][interface_name] ): raise ValueError( f"Could not find interface '{interface_name}'" f" in manifest '{valid_path}'." ) interfaces[file_path][interface_name] = { "type": "json", "code": contents["contractTypes"][interface_name]["abi"], } # ABI JSON file (either `List[ABI]` or `{"abi": List[ABI]}`) elif isinstance(contents, list) or ( "abi" in contents and isinstance(contents["abi"], list) ): interfaces[file_path][interface_name] = {"type": "json", "code": contents} else: raise ValueError(f"Corrupted file: '{valid_path}'") else: interfaces[file_path][interface_name] = {"type": "vyper", "code": code} return interfaces
def test_extract_file_interface_imports_raises(code, assert_compile_failed): assert_compile_failed(lambda: extract_file_interface_imports(code), StructureException)
def test_extract_file_interface_imports(code): assert extract_file_interface_imports(code[0]) == {"Foo": code[1]}
def get_interface_codes( root_path: Union[Path, None], contract_path: ContractPath, contract_sources: ContractCodes, interface_sources: Dict, ) -> Dict: interface_codes: Dict = {} interfaces: Dict = {} code = contract_sources[contract_path] interface_codes = extract_file_interface_imports(code) for interface_name, interface_path in interface_codes.items(): # If we know the interfaces already (e.g. EthPM Manifest file) if interface_name in interface_sources: interfaces[interface_name] = interface_sources[interface_name] continue path = Path(contract_path).parent.joinpath(interface_path).as_posix() keys = [_standardize_path(path)] if not interface_path.startswith("."): keys.append(interface_path) key = next((i for i in keys if i in interface_sources), None) if key: interfaces[interface_name] = interface_sources[key] continue key = next((i + ".vy" for i in keys if i + ".vy" in contract_sources), None) if key: interfaces[interface_name] = { "type": "vyper", "code": contract_sources[key] } continue if root_path is None: raise FileNotFoundError( f"Cannot locate interface '{interface_path}{{.vy,.json}}'") parent_path = root_path.joinpath(contract_path).parent base_paths = [parent_path] if not interface_path.startswith("."): base_paths.append(root_path) elif interface_path.startswith("../") and len( Path(contract_path).parent.parts) < Path( interface_path).parts.count(".."): raise FileNotFoundError( f"{contract_path} - Cannot perform relative import outside of base folder" ) valid_path = get_interface_file_path(base_paths, interface_path) with valid_path.open() as fh: code = fh.read() if valid_path.suffix == ".json": code_dict = json.loads(code.encode()) # EthPM Manifest v3 (EIP-2678) if "contractTypes" in code_dict: if interface_name not in code_dict["contractTypes"]: raise JSONError( f"'{interface_name}' not found in '{valid_path}'") if "abi" not in code_dict["contractTypes"][interface_name]: raise JSONError( f"Missing abi for '{interface_name}' in '{valid_path}'" ) abi = code_dict["contractTypes"][interface_name]["abi"] interfaces[interface_name] = {"type": "json", "code": abi} # ABI JSON (`{"abi": List[ABI]}`) elif "abi" in code_dict: interfaces[interface_name] = { "type": "json", "code": code_dict["abi"] } # ABI JSON (`List[ABI]`) elif isinstance(code_dict, list): interfaces[interface_name] = { "type": "json", "code": code_dict } else: raise JSONError(f"Unexpected type in file: '{valid_path}'") else: interfaces[interface_name] = {"type": "vyper", "code": code} return interfaces
def compile(self, filename): """ Compile a single source contract at :code:`filename` :param filename: Source contract's filename """ log.info("Compiling contract {}".format(filename)) # Get our ouput FS stuff ready source_file = Path(self.dir, filename) name, ext = get_filename_and_ext(filename) contract_outdir = Path(self.builddir, name) contract_outdir.mkdir(mode=0o755, exist_ok=True, parents=True) bin_outfile = contract_outdir.joinpath('{}.bin'.format(name)) abi_outfile = contract_outdir.joinpath('{}.abi'.format(name)) if ext == 'sol': if is_solidity_interface_only(source_file): log.warning("{} appears to be a Solidity interface. Skipping.".format(name)) return # Compiler command to run compile_cmd = [ SOLC_PATH, '--bin', '--optimize', '--overwrite', '--allow-paths', str(self.project_dir), '-o', str(bin_outfile.parent), str(source_file) ] log.debug("Executing compiler with: {}".format(' '.join(compile_cmd))) abi_cmd = [ SOLC_PATH, '--abi', '--overwrite', '--allow-paths', str(self.project_dir), '-o', str(abi_outfile.parent), str(source_file) ] log.debug("Executing compiler with: {}".format(' '.join(abi_cmd))) # Do the needful p_bin = Popen(compile_cmd, stdout=PIPE, stderr=STDOUT) p_abi = Popen(abi_cmd, stdout=PIPE, stderr=STDOUT) # Twiddle our thumbs p_bin.wait() p_abi.wait() # Check the output # p_bin_out = p_bin.stdout.read() # p_abi_out = p_abi.stdout.read() # solc version differences? # if ( # b'Compiler run successful' not in p_bin_out # and b'Compiler run successful' not in p_abi_out # ): # log.error("Compiler shows an error:") # raise CompileError("solc did not indicate success.") # Check the return codes compile_retval = p_bin.returncode abi_retval = p_abi.returncode if compile_retval != 0 or abi_retval != 0: raise CompileError("Solidity compiler returned non-zero exit code") if bin_outfile.stat().st_size == 0: raise CompileError( "Zero length bytecode output from compiler. This has only been seen to occur if" " there was a silent error by the Solidity compileer" ) elif ext == 'vy': source_text = '' with source_file.open() as _file: source_text = _file.read() if not source_text: # TODO: Do we want to die in a fire here? log.warning("Source file for {} appears to be empty!".format(name)) return if is_vyper_interface(source_text): log.warning("{} appears to be a Vyper interface. Skipping.".format(name)) return # Read in the source for the interface(s) interface_imports = extract_file_interface_imports(source_text) interface_codes = dict() for interface_name, interface_path in interface_imports.items(): interface_filepath = vyper_import_to_file_paths(self.dir, interface_path) with interface_filepath.open() as _file: interface_codes[interface_name] = { 'type': 'vyper', 'code': _file.read() } compiler_out = vyper.compile_code( source_text, ['bytecode', 'abi'], interface_codes=interface_codes, ) if not compiler_out.get('bytecode') and not compiler_out.get('abi'): log.error("Nothing returned by vyper compiler for {}".format(name)) return if not compiler_out.get('bytecode'): log.warning("No bytecode returned by vyper compiler for contract {}".format(name)) else: # Create the output file and open for writing of bytecode with bin_outfile.open(mode='w') as out: out.write(compiler_out['bytecode']) # ABI if not compiler_out.get('abi'): log.warning("No ABI returned by vyper compiler for contract {}".format(name)) else: # Create the output file and open for writing of bytecode with abi_outfile.open(mode='w') as out: out.write(json.dumps(compiler_out['abi'])) else: raise CompileError("Unsupported source file type")