Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Jun 1, 2024
2 parents e75a007 + bf9aa6b commit cf35d82
Show file tree
Hide file tree
Showing 34 changed files with 1,284 additions and 1,220 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
# TODO: Replace with macos-latest when works again.
# https://github.com/actions/setup-python/issues/808
os: [ubuntu-latest, macos-12] # eventually add `windows-latest`
python-version: [3.8, 3.9, '3.10', '3.11', '3.12']
python-version: [3.9, '3.10', '3.11', '3.12']

env:
GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ repos:
rev: 0.7.17
hooks:
- id: mdformat
additional_dependencies: [mdformat-gfm, mdformat-frontmatter]
additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-pyproject]


default_language_version:
Expand Down
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Compile Solidity contracts.

## Dependencies

- [python3](https://www.python.org/downloads) version 3.8 up to 3.12.
- [python3](https://www.python.org/downloads) version 3.9 up to 3.12.

## Installation

Expand Down Expand Up @@ -62,32 +62,32 @@ solidity:

### Dependency Mapping

To configure import remapping, use your project's `ape-config.yaml` file:

```yaml
solidity:
import_remapping:
- "@openzeppelin=path/to/open_zeppelin/contracts"
```

If you are using the `dependencies:` key in your `ape-config.yaml`, `ape` can automatically
search those dependencies for the path.
By default, `ape-solidity` knows to look at installed dependencies for potential remapping-values and will use those when it notices you are importing them.
For example, if you are using dependencies like:

```yaml
dependencies:
- name: OpenZeppelin
- name: openzeppelin
github: OpenZeppelin/openzeppelin-contracts
version: 4.4.2
solidity:
import_remapping:
- "@openzeppelin=OpenZeppelin/4.4.2"
```

Once you have your dependencies configured, you can import packages using your import keys:
And your source files import from `openzeppelin` this way:

```solidity
import "@openzeppelin/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
```

Ape knows how to resolve the `@openzeppelin` value and find the correct source.

If you want to override this behavior or add new remappings that are not dependencies, you can add them to your `ape-config.yaml` under the `solidity:` key.
For example, let's say you have downloaded `openzeppelin` somewhere and do not have it installed in Ape.
You can map to your local install of `openzeppelin` this way:

```yaml
solidity:
import_remapping:
- "@openzeppelin=path/to/openzeppelin"
```

### Library Linking
Expand Down
3 changes: 2 additions & 1 deletion ape_solidity/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ape import plugins

from .compiler import Extension, SolidityCompiler, SolidityConfig
from ._utils import Extension
from .compiler import SolidityCompiler, SolidityConfig


@plugins.register(plugins.Config)
Expand Down
106 changes: 10 additions & 96 deletions ape_solidity/_utils.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import json
import os
import re
from collections.abc import Iterable
from enum import Enum
from pathlib import Path
from typing import Dict, List, Optional, Sequence, Set, Union
from typing import Optional, Union

from ape.exceptions import CompilerError
from ape.utils import pragma_str_to_specifier_set
from packaging.specifiers import SpecifierSet
from packaging.version import InvalidVersion
from packaging.version import Version
from packaging.version import Version as _Version
from pydantic import BaseModel, field_validator
from solcx.install import get_executable
from solcx.wrapper import get_solc_version as get_solc_version_from_binary

from ape_solidity.exceptions import IncorrectMappingFormatError

OUTPUT_SELECTION = [
"abi",
"bin-runtime",
Expand All @@ -32,87 +27,11 @@ class Extension(Enum):
SOL = ".sol"


class ImportRemapping(BaseModel):
entry: str
packages_cache: Path

@field_validator("entry", mode="before")
@classmethod
def validate_entry(cls, value):
if len((value or "").split("=")) != 2:
raise IncorrectMappingFormatError()

return value

@property
def _parts(self) -> List[str]:
return self.entry.split("=")

# path normalization needed in case delimiter in remapping key/value
# and system path delimiter are different (Windows as an example)
@property
def key(self) -> str:
return os.path.normpath(self._parts[0])

@property
def name(self) -> str:
suffix_str = os.path.normpath(self._parts[1])
return suffix_str.split(os.path.sep)[0]

@property
def package_id(self) -> Path:
suffix = Path(self._parts[1])
data_folder_cache = self.packages_cache / suffix

try:
_Version(suffix.name)
if not suffix.name.startswith("v"):
suffix = suffix.parent / f"v{suffix.name}"

except InvalidVersion:
# The user did not specify a version_id suffix in their mapping.
# We try to smartly figure one out, else error.
if len(Path(suffix).parents) == 1 and data_folder_cache.is_dir():
version_ids = [d.name for d in data_folder_cache.iterdir()]
if len(version_ids) == 1:
# Use only version ID available.
suffix = suffix / version_ids[0]

elif not version_ids:
raise CompilerError(f"Missing dependency '{suffix}'.")

else:
options_str = ", ".join(version_ids)
raise CompilerError(
"Ambiguous version reference. "
f"Please set import remapping value to {suffix}/{{version_id}} "
f"where 'version_id' is one of '{options_str}'."
)

return suffix


class ImportRemappingBuilder:
def __init__(self, contracts_cache: Path):
# import_map maps import keys like `@openzeppelin/contracts`
# to str paths in the compiler cache folder.
self.import_map: Dict[str, str] = {}
self.dependencies_added: Set[Path] = set()
self.contracts_cache = contracts_cache

def add_entry(self, remapping: ImportRemapping):
path = remapping.package_id
if self.contracts_cache not in path.parents:
path = self.contracts_cache / path

self.import_map[remapping.key] = str(path)


def get_import_lines(source_paths: Set[Path]) -> Dict[Path, List[str]]:
imports_dict: Dict[Path, List[str]] = {}
def get_import_lines(source_paths: Iterable[Path]) -> dict[Path, list[str]]:
imports_dict: dict[Path, list[str]] = {}
for filepath in source_paths:
import_set = set()
if not filepath.is_file():
if not filepath or not filepath.is_file():
continue

source_lines = filepath.read_text().splitlines()
Expand Down Expand Up @@ -168,7 +87,7 @@ def get_pragma_spec_from_str(source_str: str) -> Optional[SpecifierSet]:
return pragma_str_to_specifier_set(pragma_match.groups()[0])


def load_dict(data: Union[str, dict]) -> Dict:
def load_dict(data: Union[str, dict]) -> dict:
return data if isinstance(data, dict) else json.loads(data)


Expand All @@ -183,17 +102,12 @@ def add_commit_hash(version: Union[str, Version]) -> Version:
return get_solc_version_from_binary(solc, with_commit_hash=True)


def verify_contract_filepaths(contract_filepaths: Sequence[Path]) -> Set[Path]:
invalid_files = [p.name for p in contract_filepaths if p.suffix != Extension.SOL.value]
if not invalid_files:
return set(contract_filepaths)

sources_str = "', '".join(invalid_files)
raise CompilerError(f"Unable to compile '{sources_str}' using Solidity compiler.")
def get_versions_can_use(pragma_spec: SpecifierSet, options: Iterable[Version]) -> list[Version]:
return sorted(list(pragma_spec.filter(options)), reverse=True)


def select_version(pragma_spec: SpecifierSet, options: Sequence[Version]) -> Optional[Version]:
choices = sorted(list(pragma_spec.filter(options)), reverse=True)
def select_version(pragma_spec: SpecifierSet, options: Iterable[Version]) -> Optional[Version]:
choices = get_versions_can_use(pragma_spec, options)
return choices[0] if choices else None


Expand Down
Loading

0 comments on commit cf35d82

Please sign in to comment.