Skip to content

Commit

Permalink
feat: upgrade to ape 0.7 (#41)
Browse files Browse the repository at this point in the history
Co-authored-by: Juliya Smith <[email protected]>
  • Loading branch information
NotPeopling2day and antazoey authored Dec 19, 2023
1 parent 73b0363 commit 4742755
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 42 deletions.
1 change: 0 additions & 1 deletion .mdformat.toml

This file was deleted.

6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-yaml

Expand All @@ -10,7 +10,7 @@ repos:
- id: isort

- repo: https://github.com/psf/black
rev: 23.9.1
rev: 23.12.0
hooks:
- id: black
name: black
Expand All @@ -21,7 +21,7 @@ repos:
- id: flake8

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.1
rev: v1.7.1
hooks:
- id: mypy
additional_dependencies: [types-setuptools, pydantic]
Expand Down
96 changes: 77 additions & 19 deletions ape_ledger/accounts.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import json
from pathlib import Path
from typing import Dict, Iterator, Optional, Union
from typing import Any, Dict, Iterator, Optional

import click
import rich
from ape.api import AccountAPI, AccountContainerAPI, TransactionAPI
from ape.types import AddressType, MessageSignature, TransactionSignature
from ape_ethereum.transactions import DynamicFeeTransaction, StaticFeeTransaction
from eth_account.messages import SignableMessage
from dataclassy import asdict
from eip712 import EIP712Message, EIP712Type
from eth_account.messages import SignableMessage, encode_defunct
from eth_pydantic_types import HexBytes
from eth_utils import is_0x_prefixed, to_bytes
from hexbytes import HexBytes

from ape_ledger.client import LedgerDeviceClient, get_device
from ape_ledger.exceptions import LedgerSigningError
from ape_ledger.hdpath import HDAccountPath


def _to_bytes(val):
def _to_bytes(val) -> bytes:
if val is None:
return b""
elif isinstance(val, str) and is_0x_prefixed(val):
Expand Down Expand Up @@ -65,8 +67,28 @@ def delete_account(self, alias: str):
path.unlink(missing_ok=True)


def _echo_object_to_sign(obj: Union[TransactionAPI, SignableMessage]):
click.echo(f"{obj}\nPlease follow the prompts on your device.")
def _echo_object_to_sign(obj: Any):
suffix = "Please follow the prompts on your device."
if isinstance(obj, EIP712Message):

def make_str(val) -> str:
if isinstance(val, dict):
return ", ".join([f"{k}={make_str(v)}" for k, v in val.items()])
elif isinstance(val, EIP712Type):
subfields_str = make_str(asdict(val))
return f"{repr(val)}({subfields_str})"
elif isinstance(val, (tuple, list, set)):
inner = ", ".join([make_str(x) for x in val])
return f"[{inner}]"
else:
return f"{val}"

fields_str = make_str(obj._body_["message"])
message_str = f"{repr(obj)}({fields_str})"
else:
message_str = f"{obj}"

rich.print(f"{message_str}\n{suffix}")


class LedgerAccount(AccountAPI):
Expand Down Expand Up @@ -94,20 +116,56 @@ def hdpath(self) -> HDAccountPath:
def account_file(self) -> dict:
return json.loads(self.account_file_path.read_text())

def sign_message(self, msg: SignableMessage) -> Optional[MessageSignature]:
version = msg.version
if version == b"E":
_echo_object_to_sign(msg)
signed_msg = self._client.sign_message(msg.body)
elif version == b"\x01":
_echo_object_to_sign(msg)
header = _to_bytes(msg.header)
body = _to_bytes(msg.body)
def sign_message(self, msg: Any, **signer_options) -> Optional[MessageSignature]:
use_eip712_package = isinstance(msg, EIP712Message)
use_eip712 = use_eip712_package
if isinstance(msg, str):
msg_to_sign = encode_defunct(text=msg)
elif isinstance(msg, int):
msg_to_sign = encode_defunct(hexstr=HexBytes(msg).hex())
elif isinstance(msg, bytes):
msg_to_sign = encode_defunct(primitive=msg)
elif use_eip712_package:
# Using eip712 package.
msg_to_sign = msg.signable_message
elif isinstance(msg, SignableMessage):
if msg.version == b"\x01":
# Using EIP-712 without eip712 package.
use_eip712 = True
elif msg.version != b"E":
try:
version_str = msg.version.decode("utf8")
except Exception:
try:
version_str = HexBytes(msg.version).hex()
except Exception:
version_str = "<UnknownVersion>"

raise LedgerSigningError(
f"Unsupported message-signing specification, (version={version_str})."
)

msg_to_sign = msg
else:
type_name = getattr(type(msg), "__name__", None)
if not type_name:
try:
type_name = str(type(msg))
except Exception:
type_name = "<UnknownType>"

raise LedgerSigningError(f"Cannot sign messages of type '{type_name}'.")

# Echo original message.
_echo_object_to_sign(msg)

if use_eip712:
header = HexBytes(msg_to_sign.header)
body = HexBytes(msg_to_sign.body)
signed_msg = self._client.sign_typed_data(header, body)

else:
raise LedgerSigningError(
f"Unsupported message-signing specification, (version={version!r})."
)
signed_msg = self._client.sign_message(msg_to_sign.body)

v, r, s = signed_msg
return MessageSignature(v=v, r=HexBytes(r), s=HexBytes(s))
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ force_grid_wrap = 0
include_trailing_comma = true
multi_line_output = 3
use_parentheses = true

[tool.mdformat]
number = true
22 changes: 12 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@
"pytest-xdist", # multi-process runner
"pytest-cov", # Coverage analyzer plugin
"pytest-mock", # For creating mocks
"eip712",
"hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer
],
"lint": [
"black>=23.9.1,<24", # auto-formatter and linter
"mypy>=1.5.1,<2", # Static type analyzer
"types-setuptools", # Needed due to mypy typeshed
"black>=23.12.0,<24", # Auto-formatter and linter
"mypy>=1.7.1,<2", # Static type analyzer
"types-setuptools", # Needed for mypy type shed
"flake8>=6.1.0,<7", # Style linter
"flake8-breakpoint>=1.1.0,<2", # Detect breakpoints left in code
"flake8-print>=5.0.0,<6", # Detect print statements left in code
"isort>=5.10.1,<6", # Import sorting linter
"mdformat>=0.7.17", # Auto-formatter for markdown
"mdformat-gfm>=0.3.5", # Needed for formatting GitHub-flavored markdown
"mdformat-frontmatter>=0.4.1", # Needed for frontmatters-style headers in issue templates
"pydantic<2.0", # Needed for successful type check. TODO: Remove after full v2 support.
"mdformat-pyproject>=0.0.1", # Allows configuring in pyproject.toml
],
"release": [ # `release` GitHub Action job uses this
"setuptools", # Installation tool
Expand Down Expand Up @@ -60,15 +61,16 @@
url="https://github.com/ApeWorX/ape-ledger",
include_package_data=True,
install_requires=[
"eth-ape>=0.7.0,<0.8",
"ledgereth>=0.9.1,<0.10",
"click", # Use same version as eth-ape
"eth-ape>=0.6.0,<0.7",
"ledgereth>=0.8.1,<0.9",
"importlib-metadata",
"rich", # Use same version as eth-ape
# ApeWorX-owned
"ethpm-types", # Use same version as eth-ape
"eip712", # Use same version as eth-ape
# EF Dependencies
"eth-account", # Use same version as eth-ape
"eth-typing", # Influenced by eth-ape
"eth-utils", # Use same version as eth-ape
"hexbytes", # Use same version as eth-ape
],
entry_points={
"ape_cli_subcommands": [
Expand Down
9 changes: 3 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

import pytest
from ape import accounts, networks
from ape.api.accounts import AccountContainerAPI
from click.testing import CliRunner
from eth_account.messages import encode_defunct
from ethpm_types import HexBytes

from ape_ledger.client import LedgerDeviceClient
from eth_pydantic_types import HexBytes

TEST_ALIAS = "TestAlias"
TEST_HD_PATH = "m/44'/60'/{x}'/0/0"
Expand Down Expand Up @@ -81,7 +78,7 @@ def tx_signature(receipt):

@pytest.fixture(autouse=True)
def mock_device(mocker, hd_path, account_addresses, msg_signature, tx_signature):
device = mocker.MagicMock(spec=LedgerDeviceClient)
device = mocker.MagicMock()
device._account = hd_path
device.get_address.side_effect = (
lambda *args, **kwargs: account_addresses[args[0]] if args else account_addresses[0]
Expand All @@ -94,7 +91,7 @@ def mock_device(mocker, hd_path, account_addresses, msg_signature, tx_signature)

@pytest.fixture
def mock_container(mocker):
return mocker.MagicMock(spec=AccountContainerAPI)
return mocker.MagicMock()


@pytest.fixture
Expand Down
8 changes: 5 additions & 3 deletions tests/test_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from ape_ethereum.ecosystem import DynamicFeeTransaction, StaticFeeTransaction
from eip712.messages import EIP712Message, EIP712Type
from eth_account.messages import SignableMessage
from eth_pydantic_types import HexBytes

from ape_ledger.accounts import AccountContainer, LedgerAccount
from ape_ledger.exceptions import LedgerSigningError
Expand Down Expand Up @@ -50,7 +51,7 @@ def build_transaction(
txn.nonce = 0
txn.gas_limit = 2
txn.value = 10000000000
txn.data = TEST_TXN_DATA
txn.data = HexBytes(TEST_TXN_DATA)

if receiver:
txn.receiver = receiver
Expand Down Expand Up @@ -144,7 +145,7 @@ def test_sign_message_typed(self, account, capsys, msg_signature):
v, r, s = account.sign_message(message)
assert (v, int(r.hex(), 16), int(s.hex(), 16)) == msg_signature
output = capsys.readouterr()
assert str(message) in output.out
assert repr(message).replace("\n", "") in output.out.replace("\n", "")
assert "Please follow the prompts on your device." in output.out

def test_sign_message_unsupported(self, account, capsys):
Expand All @@ -154,7 +155,8 @@ def test_sign_message_unsupported(self, account, capsys):
header=b"thereum Signed Message:\n6",
body=b"I\xe2\x99\xa5SF",
)
expected = rf"Unsupported message-signing specification, \(version={unsupported_version}\)."
version_str = unsupported_version.decode("utf8")
expected = rf"Unsupported message-signing specification, \(version={version_str}\)\."
with pytest.raises(LedgerSigningError, match=expected):
account.sign_message(message)

Expand Down

0 comments on commit 4742755

Please sign in to comment.