Skip to content

Commit

Permalink
Merge branch 'main' into fix/no-explorer-with-abi
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Aug 26, 2024
2 parents 1b70c55 + a2ed551 commit f5d2459
Show file tree
Hide file tree
Showing 36 changed files with 677 additions and 157 deletions.
9 changes: 4 additions & 5 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,10 @@ jobs:
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=pr
type=ref,event=branch,branch=main,latest=true
type=ref,event=branch,branch=main
type=ref,event=branch,branch!=main
type=semver,pattern={{version}},branch=main,latest=false
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=ref,event=tag
type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/') }}
type=ref,event=pr
- name: Show tags
run: |
Expand All @@ -61,6 +59,7 @@ jobs:
with:
context: .
file: ./Dockerfile.slim
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}-slim
labels: ${{ steps.meta.outputs.labels }}
build-args: |
Expand Down
2 changes: 1 addition & 1 deletion docs/_templates/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
<script src="{{ pathto('_static/js/html5shiv.min.js', 1) }}"></script>
<![endif]-->
{%- if not embedded %}
{# XXX Sphinx 1.8.0 made this an external js-file, quick fix until we refactor the template to inherert more blocks directly from sphinx #}
{# XXX Sphinx 1.8.0 made this an external js-file, quick fix until we refactor the template to inherent more blocks directly from sphinx #}
{%- if sphinx_version_info >= (1, 8) -%}
{%- for scriptfile in script_files %}
{{ js_tag(scriptfile) }}
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
autodoc_default_options = {
"exclude-members": (
"__repr__, __weakref__, __metaclass__, __init__, __format__, __new__, __str__, __dir__,"
"model_config, model_fields, model_post_init, model_computed_fields,"
"model_config, model_dump, model_fields, model_post_init, model_computed_fields,"
"__ape_extra_attributes__,"
)
}
Expand Down
1 change: 1 addition & 0 deletions docs/methoddocs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
.. automodule:: ape.api.convert
:members:
:show-inheritance:
:special-members:
```

## Explorers
Expand Down
2 changes: 1 addition & 1 deletion docs/userguides/clis.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ To specify the network option, use values like:
```

To use default values automatically, omit sections of the choice, but leave the semi-colons for parsing.
For example, `::test` means use the default ecosystem and network and the `test` provider.
For example, `::test` means to use the default ecosystem and network and the `test` provider.

Use `ecosystem`, `network`, and `provider` argument names in your command implementation to access their corresponding class instances:

Expand Down
2 changes: 1 addition & 1 deletion docs/userguides/console.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Commands:
cache Query from caching database
compile Compile select contract source files
console Load the console
init Initalize an ape project
init Initialize an ape project
networks Manage networks
plugins Manage ape plugins
run Run scripts from the `scripts/` folder
Expand Down
2 changes: 1 addition & 1 deletion docs/userguides/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Simply copy your Python logic into an Ape script and run it via:
ape run <my-deploy-script>
```

Learn how to do this and scripting in its entirity by reviewing [the scripting user-guide](./scripts.html).
Learn how to do this and scripting in its entirety by reviewing [the scripting user-guide](./scripts.html).

**There is no root `ape` command to deploy contracts; only the scripting-system, the `console`, or merely using Ape as a Python library**.

Expand Down
9 changes: 8 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ include = '\.pyi?$'

[tool.pytest.ini_options]
norecursedirs = "projects"
addopts = "-p no:ape_test" # NOTE: Prevents the ape plugin from activating on our tests

# NOTE: 'no:ape_test' Prevents the ape plugin from activating on our tests
# And 'pytest_ethereum' is not used and causes issues in some environments.
addopts = """
-p no:ape_test
-p no:pytest_ethereum
"""

python_files = "test_*.py"
testpaths = "tests"
markers = """fuzzing: Run Hypothesis fuzz test suite
Expand Down
9 changes: 4 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,11 @@
"trie>=3.0.1,<4", # Peer: stricter pin needed for uv support.
"web3[tester]>=6.17.2,<7",
# ** Dependencies maintained by ApeWorX **
# Missing pins are dependent on ETH-prefixed dependencies.
"eip712",
"ethpm-types",
"eth_pydantic_types",
"eip712>=0.2.10,<0.3",
"ethpm-types>=0.6.17,<0.7",
"eth_pydantic_types>=0.1.3,<0.2",
"evmchains>=0.0.10,<0.1",
"evm-trace",
"evm-trace>=0.2.3,<0.3",
],
entry_points={
"console_scripts": ["ape=ape._cli:cli"],
Expand Down
18 changes: 18 additions & 0 deletions src/ape/api/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,22 @@ def convert(self, value: Any) -> ConvertedType:
Convert the given value to the type specified as the generic for this class.
Implementations of this API must throw a :class:`~ape.exceptions.ConversionError`
when the item fails to convert properly.
Usage example::
from ape import convert
from ape.types import AddressType
convert("1 gwei", int)
# 1000000000
convert("1 ETH", int)
# 1000000000000000000
convert("0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5", bytes)
# HexBytes('0x283af0b28c62c092c9727f1ee09c02ca627eb7f5')
convert("vitalik.eth", AddressType) # with ape-ens plugin installed
# '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
"""
22 changes: 18 additions & 4 deletions src/ape/api/networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1017,15 +1017,26 @@ def providers(self): # -> dict[str, Partial[ProviderAPI]]
from ape.plugins._utils import clean_plugin_name

providers = {}
for _, plugin_tuple in self.plugin_manager.providers:
for _, plugin_tuple in self._get_plugin_providers():
ecosystem_name, network_name, provider_class = plugin_tuple
provider_name = clean_plugin_name(provider_class.__module__.split(".")[0])

is_custom_with_config = self._is_custom and self.default_provider_name == provider_name
# NOTE: Custom networks that are NOT from config must work with any provider.
# Also, ensure we are only adding forked providers for forked networks and
# non-forking providers for non-forked networks. For custom networks, it
# can be trickier (see last condition).
# TODO: In 0.9, add a better way for class-level ForkedProviders to define
# themselves as "Fork" providers.
if (
self.is_adhoc
or (self.ecosystem.name == ecosystem_name and self.name == network_name)
or (self._is_custom and self.default_provider_name == provider_name)
or (
is_custom_with_config
and (
(self.is_fork and "Fork" in provider_class.__name__)
or (not self.is_fork and "Fork" not in provider_class.__name__)
)
)
):
# NOTE: Lazily load provider config
providers[provider_name] = partial(
Expand All @@ -1037,6 +1048,10 @@ def providers(self): # -> dict[str, Partial[ProviderAPI]]

return providers

def _get_plugin_providers(self):
# NOTE: Abstracted for testing purposes.
return self.plugin_manager.providers

def get_provider(
self,
provider_name: Optional[str] = None,
Expand Down Expand Up @@ -1080,7 +1095,6 @@ def get_provider(
# If it can fork Ethereum (and we are asking for it) assume it can fork this one.
# TODO: Refactor this approach to work for custom-forked non-EVM networks.
common_forking_providers = self.network_manager.ethereum.mainnet_fork.providers

if provider_name in self.providers:
provider = self.providers[provider_name](provider_settings=provider_settings)
return _set_provider(provider)
Expand Down
1 change: 0 additions & 1 deletion src/ape/api/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,6 @@ def return_value(self) -> Any:
Obtain the final return value of the call. Requires tracing to function,
since this is not available from the receipt object.
"""

if trace := self.trace:
ret_val = trace.return_value
return ret_val[0] if isinstance(ret_val, tuple) and len(ret_val) == 1 else ret_val
Expand Down
23 changes: 19 additions & 4 deletions src/ape/cli/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@

from ape.api.accounts import AccountAPI
from ape.api.providers import ProviderAPI
from ape.exceptions import AccountsError
from ape.exceptions import (
AccountsError,
EcosystemNotFoundError,
NetworkNotFoundError,
ProviderNotFoundError,
)
from ape.types import _LazySequence
from ape.utils.basemodel import ManagerAccessMixin

Expand Down Expand Up @@ -195,9 +200,13 @@ def convert(
else:
alias = value

if isinstance(alias, str) and alias.startswith("TEST::"):
idx_str = value.replace("TEST::", "")
if isinstance(alias, str) and alias.upper().startswith("TEST::"):
idx_str = alias.upper().replace("TEST::", "")
if not idx_str.isnumeric():
if alias in ManagerAccessMixin.account_manager.aliases:
# Was actually a similar-alias.
return ManagerAccessMixin.account_manager.load(alias)

self.fail(f"Cannot reference test account by '{value}'.", param=param)

account_idx = int(idx_str)
Expand All @@ -209,7 +218,7 @@ def convert(
elif alias and alias in ManagerAccessMixin.account_manager.aliases:
return ManagerAccessMixin.account_manager.load(alias)

return None
self.fail(f"Account with alias '{alias}' not found.", param=param)

def print_choices(self):
choices = dict(enumerate(self.choices, 0))
Expand Down Expand Up @@ -365,6 +374,12 @@ def convert(self, value: Any, param: Optional[Parameter], ctx: Optional[Context]
# (as-is the case for custom-forked networks).
try:
choice = networks.get_provider_from_choice(network_choice=value)

except (EcosystemNotFoundError, NetworkNotFoundError, ProviderNotFoundError) as err:
# This error makes more sense, as it has attempted parsing.
# Show this message as the BadParameter message.
raise click.BadParameter(str(err)) from err

except Exception as err:
# If an error was not raised for some reason, raise a simpler error.
# NOTE: Still avoid showing the massive network options list.
Expand Down
20 changes: 10 additions & 10 deletions src/ape/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def __init__(
contract_address: Optional["AddressType"] = None,
source_traceback: _SOURCE_TRACEBACK_ARG = None,
project: Optional["ProjectManager"] = None,
set_ape_traceback: bool = False, # Overriden in ContractLogicError
set_ape_traceback: bool = False, # Overridden in ContractLogicError
):
message = message or (str(base_err) if base_err else self.DEFAULT_MESSAGE)
self.message = message
Expand All @@ -202,6 +202,7 @@ def __init__(
# Finalizes expected revert message.
super().__init__(ex_message)

self._attempted_source_traceback = False
if set_ape_traceback:
self.with_ape_traceback()

Expand Down Expand Up @@ -250,24 +251,24 @@ def trace(self, value):
def source_traceback(self) -> Optional["SourceTraceback"]:
tb = self._source_traceback
result: Optional["SourceTraceback"]
if callable(tb):
if not self._attempted_source_traceback and tb is None and self.txn is not None:
result = _get_ape_traceback_from_tx(self.txn)
# Prevent re-trying.
self._attempted_source_traceback = True
elif callable(tb):
result = tb()
self._source_traceback = result
else:
result = tb

self._source_traceback = result
return result

@source_traceback.setter
def source_traceback(self, value):
self._source_traceback = value

def _get_ape_traceback(self) -> Optional[TracebackType]:
source_tb = self.source_traceback
if not source_tb and self.txn:
source_tb = _get_ape_traceback_from_tx(self.txn)

if src_tb := source_tb:
if src_tb := self.source_traceback:
# Create a custom Pythonic traceback using lines from the sources
# found from analyzing the trace of the transaction.
if py_tb := _get_custom_python_traceback(self, src_tb, project=self._project):
Expand Down Expand Up @@ -335,7 +336,6 @@ def dev_message(self) -> Optional[str]:
Raises:
``ValueError``: When unable to get dev message.
"""

return self.source_traceback.revert_type if self.source_traceback else None

@classmethod
Expand Down Expand Up @@ -463,7 +463,7 @@ def __init__(
if options:
close_matches = difflib.get_close_matches(provider, options, cutoff=0.6)
if close_matches:
message = f"{message} Did you mean '{', '.join(close_matches)}'?"
message = f"{message}. Did you mean '{', '.join(close_matches)}'?"
else:
# No close matches. Show all provider options.
options_str = "\n".join(sorted(options))
Expand Down
2 changes: 1 addition & 1 deletion src/ape/managers/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ def install(
Args:
use_cache (bool): To force a re-install, like a refresh, set this
to ``False``.
config_override (dict): Optionally change the configurtion during install.
config_override (dict): Optionally change the configuration during install.
Returns:
:class:`~ape.managers.project.ProjectManager`: The resulting project, ready
Expand Down
48 changes: 47 additions & 1 deletion src/ape/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@
)
from ethpm_types.abi import EventABI
from ethpm_types.source import Closure
from pydantic import BaseModel, BeforeValidator, field_validator, model_validator
from pydantic import BaseModel, BeforeValidator, field_serializer, field_validator, model_validator
from pydantic_core.core_schema import (
CoreSchema,
ValidationInfo,
int_schema,
no_info_plain_validator_function,
plain_serializer_function_ser_schema,
)
from typing_extensions import TypeAlias
from web3.types import FilterParams

Expand Down Expand Up @@ -251,6 +258,15 @@ def __eq__(self, other: Any) -> bool:

return True

@field_serializer("event_arguments")
def _serialize_event_arguments(self, event_arguments, info):
"""
Because of an issue with BigInt in Pydantic,
(https://github.com/pydantic/pydantic/issues/10152)
we have to ensure these are regular ints.
"""
return {k: int(v) if isinstance(v, int) else v for k, v in event_arguments.items()}


class ContractLog(ExtraAttributesMixin, BaseContractLog):
"""
Expand Down Expand Up @@ -484,6 +500,36 @@ def __eq__(self, other: Any) -> bool:
# Try from the other end, if hasn't already.
return NotImplemented

@classmethod
def __get_pydantic_core_schema__(cls, value, handler=None) -> CoreSchema:
return no_info_plain_validator_function(
cls._validate,
serialization=plain_serializer_function_ser_schema(
cls._serialize,
info_arg=False,
return_schema=int_schema(),
),
)

@staticmethod
def _validate(value: Any, info: Optional[ValidationInfo] = None) -> "CurrencyValueComparable":
# NOTE: For some reason, for this to work, it has to happen
# in an "after" validator, or else it always only `int` type on the model.
if value is None:
# Will fail if not optional.
# Type ignore because this is an hacky and unlikely situation.
return None # type: ignore

elif isinstance(value, str) and " " in value:
return ManagerAccessMixin.conversion_manager.convert(value, int)

# For models annotating with this type, we validate all integers into it.
return CurrencyValueComparable(value)

@staticmethod
def _serialize(value):
return int(value)


CurrencyValueComparable.__name__ = int.__name__

Expand Down
1 change: 0 additions & 1 deletion src/ape/types/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,6 @@ def revert_type(self) -> Optional[str]:
The revert type, such as a builtin-error code or a user dev-message,
if there is one.
"""

return self.statements[-1].type if self.statements[-1].type != "source" else None

def append(self, __object) -> None:
Expand Down
Loading

0 comments on commit f5d2459

Please sign in to comment.