Skip to content
This repository has been archived by the owner on Dec 1, 2023. It is now read-only.

Commit

Permalink
Add get-accounts command (#119)
Browse files Browse the repository at this point in the history
* add get-accounts

* add get-account tests

* add new test key

* update readme

* remove comment

* fix docstring

* Update README.md

Co-authored-by: Martín Triay <martriay@gmail.com>

* add nre msg for get-accounts

* fix logging

* update test logs

* test nre get-accounts msgs

* add link to docs in print msg

* add link to docs, move internal func

* update test logs

* formatting

* change log to print

* update msgs in get-accounts tests

Co-authored-by: Martín Triay <martriay@gmail.com>
  • Loading branch information
andrew-fleming and martriay committed Aug 27, 2022
1 parent b51f41a commit 951cf24
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 5 deletions.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,48 @@ CONTRACT_ADDRESS2:PATH_TO_COMPILED_CONTRACT2.json
...
```

### `get-accounts`

Retrieves a list of ready-to-use accounts which allows for easy scripting integration. Before using `get-accounts`:

1. store private keys in a `.env`

```
PRIVATE_KEY_ALIAS_1=286426666527820764590699050992975838532
PRIVATE_KEY_ALIAS_2=263637040172279991633704324379452721903
PRIVATE_KEY_ALIAS_3=325047780196174231475632140485641889884
```

2. deploy accounts with the keys therefrom like this:

```bash
nile setup PRIVATE_KEY_ALIAS_1
...
nile setup PRIVATE_KEY_ALIAS_2
...
nile setup PRIVATE_KEY_ALIAS_3
...
```

Next, write a script and call `get-accounts` to retrieve and use the deployed accounts.

```python
def run(nre):
# fetch the list of deployed accounts
accounts = nre.get_accounts()
# then
accounts[0].send(...)
# or
alice, bob, *_ = accounts
alice.send(...)
bob.send(...)
```

> Please note that the list of accounts include only those that exist in the local `<network>.accounts.json` file.

## Extending Nile with plugins

Nile has the possibility of extending its CLI and `NileRuntimeEnvironment` functionalities through plugins. For developing plugins for Nile fork [this plugin example](https://github.com/franalgaba/nile-plugin-example) boilerplate and implement your desired functionality with the provided instructions.
Expand Down
4 changes: 2 additions & 2 deletions src/nile/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from nile.common import ACCOUNTS_FILENAME


def register(pubkey, address, index, network):
def register(pubkey, address, index, alias, network):
"""Register a new account."""
file = f"{network}.{ACCOUNTS_FILENAME}"

Expand All @@ -14,7 +14,7 @@ def register(pubkey, address, index, network):

with open(file, "r") as fp:
accounts = json.load(fp)
accounts[pubkey] = {"address": address, "index": index}
accounts[pubkey] = {"address": address, "index": index, "alias": alias}
with open(file, "w") as file:
json.dump(accounts, file)

Expand Down
8 changes: 8 additions & 0 deletions src/nile/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from nile.core.test import test as test_command
from nile.core.version import version as version_command
from nile.utils.debug import debug as debug_command
from nile.utils.get_accounts import get_accounts as get_accounts_command

logging.basicConfig(level=logging.DEBUG, format="%(message)s")

Expand Down Expand Up @@ -231,6 +232,13 @@ def debug(tx_hash, network, contracts_file):
debug_command(tx_hash, network, contracts_file)


@cli.command()
@network_option
def get_accounts(network):
"""Retrieve and manage deployed accounts."""
return get_accounts_command(network)


cli = load_plugins(cli)


Expand Down
5 changes: 4 additions & 1 deletion src/nile/core/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def __init__(self, signer, network):
"""Get or deploy an Account contract for the given private key."""
try:
self.signer = Signer(int(os.environ[signer]))
self.alias = signer
self.network = network
except KeyError:
logging.error(
Expand Down Expand Up @@ -55,7 +56,9 @@ def deploy(self):
overriding_path,
)

accounts.register(self.signer.public_key, address, index, self.network)
accounts.register(
self.signer.public_key, address, index, self.alias, self.network
)

return address, index

Expand Down
5 changes: 5 additions & 0 deletions src/nile/nre.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from nile.core.declare import declare
from nile.core.deploy import deploy
from nile.core.plugins import get_installed_plugins, skip_click_exit
from nile.utils.get_accounts import get_accounts


class NileRuntimeEnvironment:
Expand Down Expand Up @@ -50,3 +51,7 @@ def get_declaration(self, identifier):
def get_or_deploy_account(self, signer):
"""Get or deploy an Account contract."""
return Account(signer, self.network)

def get_accounts(self):
"""Retrieve and manage deployed accounts."""
return get_accounts(self.network)
42 changes: 42 additions & 0 deletions src/nile/utils/get_accounts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Retrieve and manage deployed accounts."""
import json
import logging

from nile.accounts import current_index
from nile.core.account import Account


def get_accounts(network):
"""Retrieve deployed accounts."""
try:
total_accounts = current_index(network)
logging.info(f"\nTotal registered accounts: {total_accounts}\n")
except FileNotFoundError:
print(f"\n❌ No registered accounts detected in {network}.accounts.json")
print("For more info, see https://github.com/OpenZeppelin/nile#get-accounts\n")
return

with open(f"{network}.accounts.json", "r") as f:
account_data = json.load(f)

accounts = []
pubkeys = list(account_data.keys())
addresses = [i["address"] for i in account_data.values()]
signers = [i["alias"] for i in account_data.values()]

for i in range(total_accounts):
logging.info(f"{i}: {addresses[i]}")

_account = _check_and_return_account(signers[i], pubkeys[i], network)
accounts.append(_account)

logging.info("\n🚀 Successfully retrieved deployed accounts")
return accounts


def _check_and_return_account(signer, pubkey, network):
account = Account(signer, network)
assert str(pubkey) == str(
account.signer.public_key
), "Signer pubkey does not match deployed pubkey"
return account
2 changes: 1 addition & 1 deletion tests/commands/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_deploy_accounts_register(mock_register, mock_deploy):
account = Account(KEY, NETWORK)

mock_register.assert_called_once_with(
account.signer.public_key, MOCK_ADDRESS, MOCK_INDEX, NETWORK
account.signer.public_key, MOCK_ADDRESS, MOCK_INDEX, KEY, NETWORK
)


Expand Down
126 changes: 126 additions & 0 deletions tests/commands/test_get_accounts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""Tests for get-accounts command."""
import logging
from unittest.mock import MagicMock, patch

import pytest

from nile.core.account import Account
from nile.utils.get_accounts import _check_and_return_account, get_accounts

NETWORK = "goerli"
PUBKEYS = [
"883045738439352841478194533192765345509759306772397516907181243450667673002",
"661519931401775515888740911132355225260405929679788917190706536765421826262",
]
ADDRESSES = ["333", "444"]
INDEXES = [0, 1]
ALIASES = ["TEST_KEY", "TEST_KEY_2"]

MOCK_ACCOUNTS = {
PUBKEYS[0]: {
"address": ADDRESSES[0],
"index": INDEXES[0],
"alias": ALIASES[0],
},
PUBKEYS[1]: {
"address": ADDRESSES[1],
"index": INDEXES[1],
"alias": ALIASES[1],
},
}


@pytest.fixture(autouse=True)
def tmp_working_dir(monkeypatch, tmp_path):
monkeypatch.chdir(tmp_path)
return tmp_path


@pytest.fixture(autouse=True)
def mock_subprocess():
with patch("nile.core.compile.subprocess") as mock_subprocess:
yield mock_subprocess


@pytest.mark.parametrize(
"private_keys, public_keys",
[
([ALIASES[0], PUBKEYS[0]]),
([ALIASES[1], PUBKEYS[1]]),
],
)
def test__check_and_return_account_with_matching_keys(private_keys, public_keys):
# Check matching public/private keys
account = _check_and_return_account(private_keys, public_keys, NETWORK)

assert type(account) is Account


@pytest.mark.parametrize(
"private_keys, public_keys",
[
([ALIASES[0], PUBKEYS[1]]),
([ALIASES[1], PUBKEYS[0]]),
],
)
def test__check_and_return_account_with_mismatching_keys(private_keys, public_keys):
# Check mismatched public/private keys
with pytest.raises(AssertionError) as err:
_check_and_return_account(private_keys, public_keys, NETWORK)

assert "Signer pubkey does not match deployed pubkey" in str(err.value)


def test_get_accounts_no_activated_accounts_feedback(capsys):
get_accounts(NETWORK)
# This test uses capsys in order to test the print statements (instead of logging)
captured = capsys.readouterr()

assert (
f"❌ No registered accounts detected in {NETWORK}.accounts.json" in captured.out
)
assert (
"For more info, see https://github.com/OpenZeppelin/nile#get-accounts"
in captured.out
)


@patch("nile.utils.get_accounts.current_index", MagicMock(return_value=len(PUBKEYS)))
@patch("nile.utils.get_accounts.open", MagicMock())
@patch("nile.utils.get_accounts.json.load", MagicMock(return_value=MOCK_ACCOUNTS))
def test_get_accounts_activated_accounts_feedback(caplog):
logging.getLogger().setLevel(logging.INFO)

# Default argument
get_accounts(NETWORK)

# Check total accounts log
assert f"\nTotal registered accounts: {len(PUBKEYS)}\n" in caplog.text

# Check index/address log
for i in range(len(PUBKEYS)):
assert f"{INDEXES[i]}: {ADDRESSES[i]}" in caplog.text

# Check final success log
assert "\n🚀 Successfully retrieved deployed accounts" in caplog.text


@patch("nile.utils.get_accounts.current_index", MagicMock(return_value=len(PUBKEYS)))
@patch("nile.utils.get_accounts.open", MagicMock())
@patch("nile.utils.get_accounts.json.load", MagicMock(return_value=MOCK_ACCOUNTS))
def test_get_accounts_with_keys():

with patch(
"nile.utils.get_accounts._check_and_return_account"
) as mock_return_account:
result = get_accounts(NETWORK)

# Check correct args are passed to `_check_and_receive_account`
for i in range(len(PUBKEYS)):
mock_return_account.assert_any_call(ALIASES[i], PUBKEYS[i], NETWORK)

# Assert call count equals correct number of accounts
assert mock_return_account.call_count == len(PUBKEYS)

# assert returned accounts array equals correct number of accounts
assert len(result) == len(PUBKEYS)
1 change: 0 additions & 1 deletion tests/test_nre.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
Only unit tests for now.
"""

from unittest.mock import patch

import click
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ description = Invoke pytest to run automated tests
setenv =
TOXINIDIR = {toxinidir}
TEST_KEY = 1234
TEST_KEY_2 = 4321
passenv =
HOME
extras =
Expand Down

0 comments on commit 951cf24

Please sign in to comment.