Ejemplo n.º 1
0
def verify_manifest(package_name: str, version: str, uri: str) -> None:
    """
    Verifies the validity of a package at a given IPFS URI.

    Arguments:
        package_name: Package name
        version: Package version
        uri: IPFS uri

    Returns None if the package is valid, raises InvalidManifest if not.
    """

    _verify_package_name(package_name)
    data = InfuraIPFSBackend().fetch_uri_contents(uri).decode("utf-8")
    try:
        manifest = json.loads(data)
    except Exception:
        raise InvalidManifest("URI did not return valid JSON encoded data")
    if json.dumps(manifest, sort_keys=True, separators=(",", ":")) != data:
        raise InvalidManifest(
            "JSON data is not tightly packed with sorted keys")
    for key, value in [
        ("manifest_version", "2"),
        ("package_name", package_name),
        ("version", version),
    ]:
        if manifest.get(key, None) != value:
            raise InvalidManifest(f"Missing or invalid field: {key}")
    try:
        process_manifest(manifest)
    except Exception as e:
        raise InvalidManifest(f"Cannot process manifest - {str(e)}")
Ejemplo n.º 2
0
def create_manifest(project_path: Path,
                    package_config: Dict,
                    pin_assets: bool = False,
                    silent: bool = True) -> Tuple[Dict, str]:
    """
    Creates a manifest from a project, and optionally pins it to IPFS.

    Arguments:
        project_path: Path to the root folder of the project
        package_config: Configuration settings for the manifest
        pin_assets: if True, all source files and the manifest will
                    be uploaded onto IPFS via Infura.

    Returns: generated manifest, ipfs uri of manifest
    """

    package_config = _remove_empty_fields(package_config)
    _verify_package_name(package_config["package_name"])

    if pin_assets:
        ipfs_backend = InfuraIPFSBackend()

    manifest = {
        "manifest_version": "2",
        "package_name": package_config["package_name"],
        "version": package_config["version"],
        "sources": {},
        "contract_types": {},
    }
    if "meta" in package_config:
        manifest["meta"] = package_config["meta"]

    # load packages.json and add build_dependencies
    packages_json: Dict = {"sources": {}, "packages": {}}
    if not package_config["settings"]["include_dependencies"]:
        installed, modified = get_installed_packages(project_path)
        if modified:
            raise InvalidManifest(
                f"Dependencies have been modified locally: {', '.join([i[0] for i in modified])}"
            )
        if installed:
            packages_json = _load_packages_json(project_path)
            manifest["build_dependencies"] = dict(
                (k, v["manifest_uri"])
                for k, v in packages_json["packages"].items())

    # add sources
    contract_path = project_path.joinpath("contracts")
    for path in contract_path.glob("**/*.sol"):
        if path.relative_to(
                project_path).as_posix() in packages_json["sources"]:
            continue
        if pin_assets:
            if not silent:
                print(
                    f'Pinning "{color("bright magenta")}{path.name}{color}"...'
                )
            uri = ipfs_backend.pin_assets(path)[0]["Hash"]
        else:
            with path.open("rb") as fp:
                uri = generate_file_hash(fp.read())
        manifest["sources"][
            f"./{path.relative_to(contract_path).as_posix()}"] = f"ipfs://{uri}"

    # add contract_types
    for path in project_path.glob("build/contracts/*.json"):
        with path.open() as fp:
            build_json = json.load(fp)
        if not build_json["bytecode"]:
            # skip contracts that cannot deploy
            continue
        if build_json["sourcePath"] in packages_json["sources"]:
            # skip dependencies
            continue
        manifest["contract_types"][
            build_json["contractName"]] = _get_contract_type(build_json)

    # add deployments
    deployment_networks = package_config["settings"]["deployment_networks"]
    if deployment_networks:
        active_network = network.show_active()
        if active_network:
            network.disconnect()
        manifest["deployments"] = {}
        if isinstance(deployment_networks, str):
            deployment_networks = [deployment_networks]
        if deployment_networks == ["*"]:
            deployment_networks = [
                i.stem for i in project_path.glob("build/deployments/*")
            ]
        for network_name in deployment_networks:
            instances = list(
                project_path.glob(f"build/deployments/{network_name}/*.json"))
            if not instances:
                continue
            instances.sort(key=lambda k: k.stat().st_mtime, reverse=True)
            network.connect(network_name)
            manifest["deployments"][web3.chain_uri] = {}
            for path in instances:
                with path.open() as fp:
                    build_json = json.load(fp)

                alias = build_json["contractName"]
                source_path = build_json["sourcePath"]
                if source_path in packages_json["sources"]:
                    alias = f"{packages_json['sources'][source_path]['packages'][0]}:{alias}"

                if alias in manifest["contract_types"]:
                    # skip deployment if bytecode does not match that of contract_type
                    bytecode = manifest["contract_types"][alias][
                        "deployment_bytecode"]["bytecode"]
                    if f"0x{build_json['bytecode']}" != bytecode:
                        continue
                else:
                    # add contract_type for dependency
                    manifest["contract_types"][alias] = _get_contract_type(
                        build_json)

                key = build_json["contractName"]
                for i in itertools.count(1):
                    if key not in manifest["deployments"][web3.chain_uri]:
                        break
                    key = f"{build_json['contractName']}-{i}"

                manifest["deployments"][web3.chain_uri][key] = {
                    "address": path.stem,
                    "contract_type": alias,
                }
            network.disconnect()
        if active_network:
            network.connect(active_network)
        if not manifest["deployments"]:
            del manifest["deployments"]

    uri = None
    if pin_assets:
        if not silent:
            print("Pinning manifest...")
        temp_path = Path(tempfile.gettempdir()).joinpath("manifest.json")
        with temp_path.open("w") as fp:
            json.dump(manifest, fp, sort_keys=True, separators=(",", ":"))
        uri = ipfs_backend.pin_assets(temp_path)[0]["Hash"]

    return manifest, uri
Ejemplo n.º 3
0
def process_manifest(manifest: Dict, uri: Optional[str] = None) -> Dict:
    """
    Processes a manifest for use with Brownie.

    Args:
        manifest: ethPM manifest
        uri: IPFS uri of the package
    """

    if manifest["manifest_version"] != "2":
        raise InvalidManifest(
            f"Brownie only supports v2 ethPM manifests, this "
            f"manifest is v{manifest['manifest_version']}")

    for key in ("contract_types", "deployments", "sources"):
        manifest.setdefault(key, {})

    # resolve sources
    for key in list(manifest["sources"]):
        content = manifest["sources"].pop(key)
        if _is_uri(content):
            content = _get_uri_contents(content)
        # ensure all absolute imports begin with contracts/
        content = re.sub(
            r"""(import((\s*{[^};]*}\s*from)|)\s*)("|')(contracts/||/)(?=[^./])""",
            lambda k: f"{k.group(1)}{k.group(4)}contracts/",
            content,
        )
        path = Path("/").joinpath(key.lstrip("./")).resolve()
        path_str = path.as_posix()[len(path.anchor):]
        manifest["sources"][f"contracts/{path_str}"] = content

    # set contract_name in contract_types
    contract_types = manifest["contract_types"]
    for key, value in contract_types.items():
        if "contract_name" not in value:
            value["contract_name"] = key

    # resolve package dependencies
    for dependency_uri in manifest.pop("build_dependencies", {}).values():
        dep_manifest = get_manifest(dependency_uri)
        for key in ("sources", "contract_types"):
            for k in [i for i in manifest[key] if i in dep_manifest[key]]:
                if manifest[key][k] != dep_manifest[key][k]:
                    raise InvalidManifest(
                        "Namespace collision between package dependencies")
            manifest[key].update(dep_manifest[key])

    # compile sources to expand contract_types
    if manifest["sources"]:
        version = compiler.find_best_solc_version(manifest["sources"],
                                                  install_needed=True)

        build_json = compiler.compile_and_format(manifest["sources"], version)
        for key, build in build_json.items():
            manifest["contract_types"].setdefault(key, {"contract_name": key})
            manifest["contract_types"][key].update({
                "abi":
                build["abi"],
                "source_path":
                build["sourcePath"],
                "all_source_paths":
                build["allSourcePaths"],
                "compiler":
                build["compiler"],
            })

    # delete contract_types with no source or ABI, we can't do much with them
    manifest["contract_types"] = dict(
        (k, v) for k, v in manifest["contract_types"].items() if "abi" in v)

    # resolve or delete deployments
    for chain_uri in list(manifest["deployments"]):
        deployments = manifest["deployments"][chain_uri]
        for name in list(deployments):
            deployments[name]["address"] = to_address(
                deployments[name]["address"])
            alias = deployments[name]["contract_type"]
            alias = alias[alias.rfind(":") + 1:]
            deployments[name]["contract_type"] = alias
            if alias not in manifest["contract_types"]:
                del deployments[name]
        if not deployments:
            del manifest["deployments"][chain_uri]

    manifest["meta_brownie"] = {"manifest_uri": uri, "registry_address": None}
    return manifest