"""
Standard crytic-compile export
"""
import json
import os
from pathlib import Path
from typing import TYPE_CHECKING, Dict, Type, List, Tuple
from crytic_compile.compiler.compiler import CompilerVersion
from crytic_compile.platform import Type as PlatformType
from crytic_compile.platform.abstract_platform import AbstractPlatform
from crytic_compile.utils.naming import Filename
# Cycle dependency
from crytic_compile.utils.natspec import Natspec
if TYPE_CHECKING:
from crytic_compile import CryticCompile
[docs]def export_to_standard(crytic_compile: "CryticCompile", **kwargs: str) -> str:
"""
Export the project to the standard crytic compile format
:param crytic_compile:
:param kwargs:
:return:
"""
# Obtain objects to represent each contract
output = generate_standard_export(crytic_compile)
export_dir = kwargs.get("export_dir", "crytic-export")
if not os.path.exists(export_dir):
os.makedirs(export_dir)
target = (
"contracts"
if os.path.isdir(crytic_compile.target)
else Path(crytic_compile.target).parts[-1]
)
path = os.path.join(export_dir, f"{target}.json")
with open(path, "w", encoding="utf8") as file_desc:
json.dump(output, file_desc)
return path
[docs]class Standard(AbstractPlatform):
"""
Standard platform (crytic-compile specific)
"""
NAME = "Standard"
PROJECT_URL = "https://github.com/crytic/crytic-compile"
TYPE = PlatformType.STANDARD
HIDE = True
def __init__(self, target: str, **kwargs: str):
"""
Initializes an object which represents solc standard json
:param target: A string path to a standard json
"""
super().__init__(str(target), **kwargs)
self._underlying_platform: Type[AbstractPlatform] = Standard
self._unit_tests: List[str] = []
[docs] def compile(self, crytic_compile: "CryticCompile", **_kwargs: str):
"""
Compile the target (load file)
:param crytic_compile:
:param target:
:param kwargs:
:return:
"""
from crytic_compile.crytic_compile import get_platforms
with open(self._target, encoding="utf8") as file_desc:
loaded_json = json.load(file_desc)
(underlying_type, unit_tests) = load_from_compile(crytic_compile, loaded_json)
underlying_type = PlatformType(underlying_type)
platforms: List[Type[AbstractPlatform]] = get_platforms()
platform = next((p for p in platforms if p.TYPE == underlying_type), Standard)
self._underlying_platform = platform
self._unit_tests = unit_tests
[docs] @staticmethod
def is_supported(target: str, **kwargs: str) -> bool:
"""
Check if the target is the standard crytic compile export
:param target:
:return:
"""
standard_ignore = kwargs.get("standard_ignore", False)
if standard_ignore:
return False
if not Path(target).parts:
return False
return Path(target).parts[-1].endswith("_export.json")
[docs] def is_dependency(self, path: str) -> bool:
"""
Always return False
:param path:
:return:
"""
# handled by crytic_compile_dependencies
return False
def _guessed_tests(self) -> List[str]:
return self._unit_tests
@property
def platform_name_used(self):
return self._underlying_platform.NAME
@property
def platform_project_url_used(self):
return self._underlying_platform.PROJECT_URL
@property
def platform_type_used(self):
return self._underlying_platform.TYPE
[docs]def generate_standard_export(crytic_compile: "CryticCompile") -> Dict:
"""
Export the standard crytic compile export
:param crytic_compile:
:return:
"""
contracts = dict()
for contract_name in crytic_compile.contracts_names:
filename = crytic_compile.filename_of_contract(contract_name)
libraries = crytic_compile.libraries_names_and_patterns(contract_name)
contracts[contract_name] = {
"abi": crytic_compile.abi(contract_name),
"bin": crytic_compile.bytecode_init(contract_name),
"bin-runtime": crytic_compile.bytecode_runtime(contract_name),
"srcmap": ";".join(crytic_compile.srcmap_init(contract_name)),
"srcmap-runtime": ";".join(crytic_compile.srcmap_runtime(contract_name)),
"filenames": {
"absolute": filename.absolute,
"used": filename.used,
"short": filename.short,
"relative": filename.relative,
},
"libraries": dict(libraries) if libraries else dict(),
"is_dependency": crytic_compile.is_dependency(filename.absolute),
"userdoc": crytic_compile.natspec[contract_name].userdoc.export(),
"devdoc": crytic_compile.natspec[contract_name].devdoc.export(),
}
# Create our root object to contain the contracts and other information.
compiler: Dict = dict()
if crytic_compile.compiler_version:
compiler = {
"compiler": crytic_compile.compiler_version.compiler,
"version": crytic_compile.compiler_version.version,
"optimized": crytic_compile.compiler_version.optimized,
}
output = {
"asts": crytic_compile.asts,
"contracts": contracts,
"compiler": compiler,
"package": crytic_compile.package,
"working_dir": str(crytic_compile.working_dir),
"type": int(crytic_compile.platform.platform_type_used),
"unit_tests": crytic_compile.platform.guessed_tests(),
}
return output
[docs]def load_from_compile(crytic_compile: "CryticCompile", loaded_json: Dict) -> Tuple[int, List[str]]:
"""
Load from json
:param crytic_compile:
:param loaded_json:
:return:
"""
crytic_compile.package_name = loaded_json.get("package", None)
crytic_compile.asts = loaded_json["asts"]
crytic_compile.compiler_version = CompilerVersion(
compiler=loaded_json["compiler"]["compiler"],
version=loaded_json["compiler"]["version"],
optimized=loaded_json["compiler"]["optimized"],
)
for contract_name, contract in loaded_json["contracts"].items():
crytic_compile.contracts_names.add(contract_name)
filename = Filename(
absolute=contract["filenames"]["absolute"],
relative=contract["filenames"]["relative"],
short=contract["filenames"]["short"],
used=contract["filenames"]["used"],
)
crytic_compile.contracts_filenames[contract_name] = filename
crytic_compile.abis[contract_name] = contract["abi"]
crytic_compile.bytecodes_init[contract_name] = contract["bin"]
crytic_compile.bytecodes_runtime[contract_name] = contract["bin-runtime"]
crytic_compile.srcmaps_init[contract_name] = contract["srcmap"].split(";")
crytic_compile.srcmaps_runtime[contract_name] = contract["srcmap-runtime"].split(";")
crytic_compile.libraries[contract_name] = contract["libraries"]
userdoc = contract.get("userdoc", {})
devdoc = contract.get("devdoc", {})
crytic_compile.natspec[contract_name] = Natspec(userdoc, devdoc)
if contract["is_dependency"]:
crytic_compile.dependencies.add(filename.absolute)
crytic_compile.dependencies.add(filename.relative)
crytic_compile.dependencies.add(filename.short)
crytic_compile.dependencies.add(filename.used)
# Set our filenames
crytic_compile.filenames = set(crytic_compile.contracts_filenames.values())
crytic_compile.working_dir = loaded_json["working_dir"]
return (loaded_json["type"], loaded_json["unit_tests"])
def _relative_to_short(relative):
"""
:param relative:
:return:
"""
return relative