Skip to content

Commit

Permalink
chore: improving tests; smoke testing
Browse files Browse the repository at this point in the history
  • Loading branch information
aorumbayev committed Oct 6, 2023
1 parent fbb0d4d commit 7dfa0d1
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 42 deletions.
38 changes: 22 additions & 16 deletions docs/features/tasks/transfer.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,41 @@ Available commands and possible usage as follows:

```bash
$ ~ algokit task transfer
Usage: algokit task transfer [OPTIONS] [[localnet|testnet|mainnet]]
Usage: algokit task transfer [OPTIONS]

Options:
-s, --sender TEXT Address or alias of the sender account [required]
-r, --receiver TEXT Address to an account that will receive the asset(s) [required]
--asset, --id INTEGER ASA asset id to transfer (defaults to 0, Algos)
-a, --amount INTEGER Amount to transfer [required]
--whole-units Use whole units (Algos | ASAs) instead of smallest divisible units (for example, microAlgos).
Disabled by default.
-h, --help Show this message and exit.
-s, --sender TEXT Address or alias of the sender account [required]
-r, --receiver TEXT Address or alias to an account that will receive the asset(s) [required]
--asset, --id INTEGER ASA asset id to transfer
-a, --amount INTEGER Amount to transfer [required]
--whole-units Use whole units (Algos | ASAs) instead of smallest divisible units (for example,
microAlgos). Disabled by default.
-n, --network [localnet|testnet|mainnet]
Network to use. Refers to `localnet` by default.
-h, --help Show this message and exit.
```

> Please note, when supplying `sender` argument as a wallet address, you will be prompted for a masked input of the mnemonic phrase.
> If you would prefer to use a wallet alias, refer to [wallet aliasing](wallet.md) task for details on creating and using wallet aliases.
> Note: If you use a wallet address for the `sender` argument, you'll be asked for the mnemonic phrase. To use a wallet alias instead, see the [wallet aliasing](wallet.md) task. For wallet aliases, the sender must have a stored `private key`, but the receiver doesn't need one. This is because the sender signs and sends the transfer transaction, while the receiver reference only needs a valid Algorand address.
## Examples

### Transfer algo between accounts
### Transfer algo between accounts on LocalNet

```bash
$ ~ algokit task transfer -s {SENDER_ALIAS OR SENDER_ADDRESS} -r {RECEIVER_ADDRESS} -a {AMOUNT}
$ ~ algokit task transfer -s {SENDER_ALIAS OR SENDER_ADDRESS} -r {RECEIVER_ALIAS OR RECEIVER_ADDRESS} -a {AMOUNT}
```

By default, the amount is in microAlgos. To use whole units, use the `--whole-units` flag.
By default:

### Transfer asset between accounts
- the `amount` is in microAlgos. To use whole units, use the `--whole-units` flag.
- the `network` is `localnet`.

### Transfer asset between accounts on TestNet

```bash
$ ~ algokit task transfer -s {SENDER_ALIAS OR SENDER_ADDRESS} -r {RECEIVER_ADDRESS} -a {AMOUNT} --id {ASSET_ID}
$ ~ algokit task transfer -s {SENDER_ALIAS OR SENDER_ADDRESS} -r {RECEIVER_ALIAS OR RECEIVER_ADDRESS} -a {AMOUNT} --id {ASSET_ID} --network testnet
```

By default, the amount is in smallest divisible unit of given asset (if exists). To use whole units, use the `--whole-units` flag.
By default:

- the `amount` is smallest divisible unit of supplied `ASSET_ID`. To use whole units, use the `--whole-units` flag.
55 changes: 32 additions & 23 deletions src/algokit/cli/tasks/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ def _get_asset_decimals(asset_id: int, algod_client: algosdk.v2client.algod.Algo

asset_info = algod_client.asset_info(asset_id)

if not isinstance(asset_info, dict):
if not isinstance(asset_info, dict) or "params" not in asset_info or "decimals" not in asset_info["params"]:
raise click.ClickException("Invalid asset info response")

return asset_info.get("decimals", 0)
return int(asset_info["params"]["decimals"])


def _validate_address(address: str) -> None:
Expand Down Expand Up @@ -103,30 +103,34 @@ def _validate_inputs(


def _get_account_with_private_key(address: str) -> Account:
parsed_address = address.strip('"')

try:
_validate_address(address)
_validate_address(parsed_address)
pk = _get_private_key_from_mnemonic()
return Account(address=address, private_key=pk)
return Account(address=parsed_address, private_key=pk)
except click.ClickException as ex:
alias_data = get_alias(address)
alias_data = get_alias(parsed_address)

if not alias_data:
raise click.ClickException(f"Alias `{address}` alias does not exist.") from ex
raise click.ClickException(f"Alias `{parsed_address}` alias does not exist.") from ex
if not alias_data.private_key:
raise click.ClickException(f"Alias `{address}` does not have a private key.") from ex
raise click.ClickException(f"Alias `{parsed_address}` does not have a private key.") from ex

return Account(address=alias_data.address, private_key=alias_data.private_key)


def _get_address_with_private_key(address: str) -> str:
def _get_address(address: str) -> str:
parsed_address = address.strip('"')

try:
_validate_address(address)
return address
_validate_address(parsed_address)
return parsed_address
except click.ClickException as ex:
alias_data = get_alias(address)
alias_data = get_alias(parsed_address)

if not alias_data:
raise click.ClickException(f"Alias `{address}` alias does not exist.") from ex
raise click.ClickException(f"Alias `{parsed_address}` alias does not exist.") from ex

return alias_data.address

Expand Down Expand Up @@ -154,7 +158,14 @@ def _get_address_with_private_key(address: str) -> str:
default=False,
required=False,
)
@click.argument("network", type=click.Choice(["localnet", "testnet", "mainnet"]), default="localnet", required=False)
@click.option(
"-n",
"--network",
type=click.Choice(["localnet", "testnet", "mainnet"]),
default="localnet",
required=False,
help="Network to use. Refers to `localnet` by default.",
)
def transfer( # noqa: PLR0913
*,
sender: str,
Expand All @@ -164,37 +175,34 @@ def transfer( # noqa: PLR0913
whole_units: bool,
network: str,
) -> None:
# Trim special characters from sender and receiver addresses
sender = (sender or "").strip('"')
receiver = (receiver or "").strip('"')

# Load addresses and accounts from mnemonics or aliases
sender_account = _get_account_with_private_key(sender)
receiver_account = _get_address_with_private_key(receiver)
receiver_address = _get_address(receiver)

# Get algod client
algod_client = _get_algod_client(network)

# Validate inputs
_validate_inputs(sender_account, receiver_account, asset_id, amount, algod_client)

# Convert amount to whole units if specified
if whole_units:
amount = amount * (10 ** _get_asset_decimals(asset_id, algod_client))

# Validate inputs
_validate_inputs(sender_account, receiver_address, asset_id, amount, algod_client)

# Transfer algos or assets depending on asset_id
txn_response: PaymentTxn | AssetTransferTxn | None = None
try:
if asset_id == 0:
txn_response = transfer_algos(
algod_client,
TransferParameters(to_address=receiver_account, from_account=sender_account, micro_algos=amount),
TransferParameters(to_address=receiver_address, from_account=sender_account, micro_algos=amount),
)
else:
txn_response = transfer_asset(
algod_client,
TransferAssetParameters(
from_account=sender_account,
to_address=receiver_account,
to_address=receiver_address,
amount=amount,
asset_id=asset_id,
),
Expand All @@ -210,4 +218,5 @@ def transfer( # noqa: PLR0913
)

except Exception as err:
logger.debug(err, exc_info=True)
raise click.ClickException("Failed to perform transfer") from err
25 changes: 25 additions & 0 deletions tests/tasks/test_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def test_transfer_invalid_sender_account() -> None:
verify(result.output)


@pytest.mark.usefixtures("mock_keyring")
def test_transfer_invalid_receiver_account() -> None:
# Arrange
dummy_sender_pk, dummy_sender_address = _generate_account()
Expand Down Expand Up @@ -134,6 +135,30 @@ def test_transfer_asset_from_address_successful(mocker: MockerFixture) -> None:
verify(result.output)


def test_transfer_asset_from_address_to_alias_successful(mocker: MockerFixture, mock_keyring: dict[str, str]) -> None:
# Arrange
mocker.patch("algokit.cli.tasks.transfer.transfer_asset", return_value=TransactionMock())
mocker.patch("algokit.cli.tasks.transfer._validate_inputs")
dummy_sender_pk, dummy_sender_address = _generate_account()
_generate_account()[1]

dummy_receiver_alias = "dummy_receiver_alias"
mock_keyring[dummy_receiver_alias] = json.dumps(
{"alias": dummy_receiver_alias, "address": dummy_sender_address, "private_key": None}
)
mock_keyring[WALLET_ALIASES_KEYRING_USERNAME] = json.dumps([dummy_receiver_alias])

# Act
result = invoke(
f"task transfer -s {dummy_sender_address} -r {dummy_receiver_alias} -a 1 --id 1234",
input=_get_mnemonic_from_private_key(dummy_sender_pk),
)

# Assert
assert result.exit_code == 0
verify(result.output)


def test_transfer_asset_from_alias_successful(mocker: MockerFixture, mock_keyring: dict[str, str]) -> None:
# Arrange
mocker.patch("algokit.cli.tasks.transfer.transfer_asset", return_value=TransactionMock())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Enter the mnemonic phrase (25 words separated by whitespace):
Successfully performed transfer. See details at https://testnet.algoexplorer.io/tx/dummy_txid
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
DEBUG: dummy error
Error: Failed to perform transfer
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Enter the mnemonic phrase (25 words separated by whitespace):
Error: invalid-address is an invalid account address
DEBUG: `invalid-address` does not exist
Error: Alias `invalid-address` alias does not exist.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Usage: algokit task transfer [OPTIONS] [[localnet|testnet|mainnet]]
Usage: algokit task transfer [OPTIONS]
Try 'algokit task transfer -h' for help.

Error: Missing option '--amount' / '-a'.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Usage: algokit task transfer [OPTIONS] [[localnet|testnet|mainnet]]
Usage: algokit task transfer [OPTIONS]
Try 'algokit task transfer -h' for help.

Error: Missing option '--sender' / '-s'.

0 comments on commit 7dfa0d1

Please sign in to comment.