Skip to content

Commit

Permalink
feat: conform hd path
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Dec 15, 2023
1 parent 25a8abb commit 5ffe9b1
Show file tree
Hide file tree
Showing 12 changed files with 50 additions and 35 deletions.
7 changes: 3 additions & 4 deletions src/ape/api/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,8 +648,6 @@ def poll_blocks( # type: ignore[empty-body]
or a ``stop_block`` is given.
Args:
start_block (Optional[int]): The block number to start with. Defaults to the pending
block number.
stop_block (Optional[int]): Optionally set a future block number to stop at.
Defaults to never-ending.
required_confirmations (Optional[int]): The amount of confirmations to wait
Expand All @@ -671,12 +669,12 @@ def poll_logs( # type: ignore[empty-body]
topics: Optional[List[Union[str, List[str]]]] = None,
required_confirmations: Optional[int] = None,
new_block_timeout: Optional[int] = None,
events: List[EventABI] = [],
events: Optional[List[EventABI]] = None,
) -> Iterator[ContractLog]:
"""
Poll new blocks. Optionally set a start block to include historical blocks.
**NOTE**: This is a daemon method; it does not terminate unless an exception occurrs.
**NOTE**: This is a daemon method; it does not terminate unless an exception occurs.
Usage example::
Expand All @@ -696,6 +694,7 @@ def poll_logs( # type: ignore[empty-body]
new_block_timeout (Optional[int]): The amount of time to wait for a new block before
quitting. Defaults to 10 seconds for local networks or ``50 * block_time`` for live
networks.
events (Optional[List[``EventABI``]]): An optional list of events to listen on.
Returns:
Iterator[:class:`~ape.types.ContractLog`]
Expand Down
4 changes: 2 additions & 2 deletions src/ape/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@
)
from ape.utils.process import JoinableQueue, spawn
from ape.utils.testing import (
DEFAULT_HD_PATH,
DEFAULT_NUMBER_OF_TEST_ACCOUNTS,
DEFAULT_TEST_CHAIN_ID,
DEFAULT_TEST_HD_PATH,
DEFAULT_TEST_MNEMONIC,
GeneratedDevAccount,
generate_dev_accounts,
Expand All @@ -71,7 +71,7 @@
"DEFAULT_NUMBER_OF_TEST_ACCOUNTS",
"DEFAULT_TEST_CHAIN_ID",
"DEFAULT_TEST_MNEMONIC",
"DEFAULT_HD_PATH",
"DEFAULT_TEST_HD_PATH",
"DEFAULT_TRANSACTION_ACCEPTANCE_TIMEOUT",
"EMPTY_BYTES32",
"expand_environment_variables",
Expand Down
17 changes: 11 additions & 6 deletions src/ape/utils/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

DEFAULT_NUMBER_OF_TEST_ACCOUNTS = 10
DEFAULT_TEST_MNEMONIC = "test test test test test test test test test test test junk"
DEFAULT_HD_PATH = "m/44'/60'/0'/{}"
DEFAULT_TEST_HD_PATH = "m/44'/60'/0'/0/"
DEFAULT_TEST_CHAIN_ID = 1337
GeneratedDevAccount = namedtuple("GeneratedDevAccount", ("address", "private_key"))
"""
Expand All @@ -28,7 +28,7 @@
def generate_dev_accounts(
mnemonic: str = DEFAULT_TEST_MNEMONIC,
number_of_accounts: int = DEFAULT_NUMBER_OF_TEST_ACCOUNTS,
hd_path_format: str = DEFAULT_HD_PATH,
hd_path: str = DEFAULT_TEST_HD_PATH,
start_index: int = 0,
) -> List[GeneratedDevAccount]:
"""
Expand All @@ -39,8 +39,8 @@ def generate_dev_accounts(
Args:
mnemonic (str): mnemonic phrase or seed words.
number_of_accounts (int): Number of accounts. Defaults to ``10``.
hd_path_format (str): Hard Wallets/HD Keys derivation path format.
Defaults to ``"m/44'/60'/0'/{}"``.
hd_path(str): Hard Wallets/HD Keys derivation path format.
Defaults to ``"m/44'/60'/0'/0"``.
start_index (int): The index to start from in the path. Defaults
to 0.
Expand All @@ -50,9 +50,14 @@ def generate_dev_accounts(
seed = Mnemonic.to_seed(mnemonic)
accounts = []

if "{}" in hd_path or "{0}" in hd_path:
hd_path_format = hd_path
else:
hd_path_format = f"{hd_path.rstrip('/')}/{{}}"

for i in range(start_index, start_index + number_of_accounts):
hd_path = HDPath(hd_path_format.format(i))
private_key = HexBytes(hd_path.derive(seed)).hex()
hd_path_obj = HDPath(hd_path_format.format(i))
private_key = HexBytes(hd_path_obj.derive(seed)).hex()
address = Account.from_key(private_key).address
accounts.append(GeneratedDevAccount(address, private_key))

Expand Down
5 changes: 3 additions & 2 deletions src/ape_accounts/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import click
from eth_account import Account as EthAccount
from eth_account.hdaccount import ETHEREUM_DEFAULT_PATH
from eth_utils import to_bytes
from eth_utils import to_bytes, to_checksum_address

from ape import accounts
from ape.cli import ape_cli_context, existing_alias_argument, non_existing_alias_argument
Expand Down Expand Up @@ -162,8 +162,9 @@ def export(cli_ctx, alias):
account = json.loads(path.read_text())
password = click.prompt("Enter password to decrypt account", hide_input=True)
private_key = EthAccount.decrypt(account, password)
address = to_checksum_address(account["address"])
cli_ctx.logger.success(
f"Account 0x{account['address']} private key: {click.style(private_key.hex(), bold=True)})"
f"Account {address} private key: {click.style(private_key.hex(), bold=True)})"
)


Expand Down
3 changes: 2 additions & 1 deletion src/ape_ethereum/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,8 +697,9 @@ def poll_logs(
topics: Optional[List[Union[str, List[str]]]] = None,
required_confirmations: Optional[int] = None,
new_block_timeout: Optional[int] = None,
events: List[EventABI] = [],
events: Optional[List[EventABI]] = None,
) -> Iterator[ContractLog]:
events = events or []
if required_confirmations is None:
required_confirmations = self.network.required_confirmations

Expand Down
7 changes: 6 additions & 1 deletion src/ape_geth/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from ape.utils import (
DEFAULT_NUMBER_OF_TEST_ACCOUNTS,
DEFAULT_TEST_CHAIN_ID,
DEFAULT_TEST_HD_PATH,
DEFAULT_TEST_MNEMONIC,
JoinableQueue,
generate_dev_accounts,
Expand Down Expand Up @@ -58,6 +59,7 @@ def __init__(
executable: Optional[str] = None,
auto_disconnect: bool = True,
extra_funded_accounts: Optional[List[str]] = None,
hd_path: Optional[str] = DEFAULT_TEST_HD_PATH,
):
executable = executable or "geth"
if not shutil.which(executable):
Expand Down Expand Up @@ -88,7 +90,9 @@ def __init__(

sealer = ensure_account_exists(**geth_kwargs).decode().replace("0x", "")
geth_kwargs["miner_etherbase"] = sealer
accounts = generate_dev_accounts(mnemonic, number_of_accounts=number_of_accounts)
accounts = generate_dev_accounts(
mnemonic, number_of_accounts=number_of_accounts, hd_path=hd_path or DEFAULT_TEST_HD_PATH
)
addresses = [a.address for a in accounts]
addresses.extend(extra_funded_accounts or [])
bal_dict = {"balance": str(initial_balance)}
Expand Down Expand Up @@ -146,6 +150,7 @@ def from_uri(cls, uri: str, data_folder: Path, **kwargs):
executable=kwargs.get("executable"),
auto_disconnect=kwargs.get("auto_disconnect", True),
extra_funded_accounts=extra_accounts,
hd_path=kwargs.get("hd_path", DEFAULT_TEST_HD_PATH),
)

def connect(self, timeout: int = 60):
Expand Down
8 changes: 4 additions & 4 deletions src/ape_test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ape import plugins
from ape.api import PluginConfig
from ape.api.networks import LOCAL_NETWORK_NAME
from ape.utils import DEFAULT_HD_PATH, DEFAULT_NUMBER_OF_TEST_ACCOUNTS, DEFAULT_TEST_MNEMONIC
from ape.utils import DEFAULT_NUMBER_OF_TEST_ACCOUNTS, DEFAULT_TEST_HD_PATH, DEFAULT_TEST_MNEMONIC
from ape_test.accounts import TestAccount, TestAccountContainer
from ape_test.provider import EthTesterProviderConfig, LocalProvider

Expand Down Expand Up @@ -93,7 +93,7 @@ class CoverageConfig(PluginConfig):
"""


class Config(PluginConfig):
class ApeTestConfig(PluginConfig):
mnemonic: str = DEFAULT_TEST_MNEMONIC
"""
The mnemonic to use when generating the test accounts.
Expand All @@ -119,7 +119,7 @@ class Config(PluginConfig):
Set to ``False`` to keep providers connected at the end of the test run.
"""

hd_path: str = DEFAULT_HD_PATH
hd_path: str = DEFAULT_TEST_HD_PATH
"""
The hd_path to use when generating the test accounts.
"""
Expand All @@ -132,7 +132,7 @@ class Config(PluginConfig):

@plugins.register(plugins.Config)
def config_class():
return Config
return ApeTestConfig


@plugins.register(plugins.AccountPlugin)
Expand Down
4 changes: 2 additions & 2 deletions src/ape_test/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def _dev_accounts(self) -> List[GeneratedDevAccount]:
return generate_dev_accounts(
self.mnemonic,
number_of_accounts=self.num_of_accounts,
hd_path_format=self.hd_path,
hd_path=self.hd_path,
)

@property
Expand Down Expand Up @@ -77,7 +77,7 @@ def generate_account(self) -> "TestAccountAPI":
new_index = self.num_of_accounts + self.num_generated
self.num_generated += 1
generated_account = generate_dev_accounts(
self.mnemonic, 1, hd_path_format=self.hd_path, start_index=new_index
self.mnemonic, 1, hd_path=self.hd_path, start_index=new_index
)[0]
acc = TestAccount(
index=new_index,
Expand Down
4 changes: 3 additions & 1 deletion src/ape_test/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
VirtualMachineError,
)
from ape.types import BlockID, SnapshotID
from ape.utils import DEFAULT_TEST_CHAIN_ID, gas_estimation_error_message
from ape.utils import DEFAULT_TEST_CHAIN_ID, DEFAULT_TEST_HD_PATH, gas_estimation_error_message
from ape_ethereum.provider import Web3Provider


Expand Down Expand Up @@ -58,9 +58,11 @@ def connect(self):
# Is already connected and settings have not changed.
return

hd_path = (self.config.hd_path or DEFAULT_TEST_HD_PATH).rstrip("/")
self._evm_backend = PyEVMBackend.from_mnemonic(
mnemonic=self.config.mnemonic,
num_accounts=self.config.number_of_accounts,
hd_path=hd_path,
)
endpoints = {**API_ENDPOINTS}
endpoints["eth"] = merge(endpoints["eth"], {"chainId": static_return(chain_id)})
Expand Down
12 changes: 6 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,22 +152,22 @@ def dependency_manager(project):
def keyparams():
# NOTE: password is 'a'
return {
"address": "7e5f4552091a69125d5dfcb7b8c2659029395bdf",
"address": "f39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"crypto": {
"cipher": "aes-128-ctr",
"cipherparams": {"iv": "7bc492fb5dca4fe80fd47645b2aad0ff"},
"ciphertext": "43beb65018a35c31494f642ec535315897634b021d7ec5bb8e0e2172387e2812",
"cipherparams": {"iv": "fe11b8a2576bacd917b02c065f764369"},
"ciphertext": "80a7a3a901cbfd4720ca77b81b14e4db87da84e0de9d2257dfd7427108c0f573",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 262144,
"r": 1,
"p": 8,
"salt": "4b127cb5ddbc0b3bd0cc0d2ef9a89bec",
"salt": "62c6413db938987b0be436335e38b4ae",
},
"mac": "6a1d520975a031e11fc16cff610f5ae7476bcae4f2f598bc59ccffeae33b1caa",
"mac": "43935d1a983e13c083e724ebc9d02d5467e0accfa6df8db1fc41f9870f5e776e",
},
"id": "ee424db9-da20-405d-bd75-e609d3e2b4ad",
"id": "87c9b6e3-5ce4-4758-ab5b-444074e594fe",
"version": 3,
}

Expand Down
4 changes: 2 additions & 2 deletions tests/functional/test_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ def test_is_not_contract(owner, keyfile_account):
def test_using_different_hd_path(test_accounts, temp_config):
test_config = {
"test": {
"hd_path": "m/44'/60'/0'/0/{}",
"hd_path": "m/44'/60'/0'/{}",
}
}

Expand Down Expand Up @@ -609,7 +609,7 @@ def test_load_public_key_from_keyfile(runner, keyfile_account):

assert (
keyfile_account.public_key.hex()
== "0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8" # noqa: 501
== "0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5" # noqa: 501
)
# no need for password when loading from the keyfile
assert keyfile_account.public_key
10 changes: 6 additions & 4 deletions tests/integration/cli/test_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,19 @@ def test_import_mnemonic_custom_hdpath(


@run_once
def test_export(ape_cli, runner, temp_keyfile):
address = json.loads(temp_keyfile.read_text())["address"]
def test_export(ape_cli, runner, temp_keyfile, keyfile_account, test_accounts):
# export key
result = runner.invoke(
ape_cli,
["accounts", "export", ALIAS],
input="\n".join([PASSWORD, PASSWORD]),
)
assert result.exit_code == 0, result.output
assert f"0x{PRIVATE_KEY}" in result.output
assert address in result.output
# NOTE: temp_keyfile uses the as the keyfile account.
assert keyfile_account.address in result.output
# NOTE: Both of these accounts are the same as the first
# test account.
assert test_accounts[0].private_key in result.output


@run_once
Expand Down

0 comments on commit 5ffe9b1

Please sign in to comment.