Skip to content

Commit

Permalink
chore: algokit utils v3 migration (#611)
Browse files Browse the repository at this point in the history
* chore: algokit utils v3 migration

* chore: temporarily set portability test algokit init against pr branch

* chore: tmp disable audit

* chore: restore tests against main branch
  • Loading branch information
aorumbayev authored Feb 19, 2025
1 parent ba91e82 commit 7344544
Show file tree
Hide file tree
Showing 15 changed files with 326 additions and 242 deletions.
2 changes: 2 additions & 0 deletions .github/actions/setup-poetry/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ runs:
- if: ${{ runner.os == 'macOS' && runner.arch == 'ARM64' }}
run: |
pip install poetry
pip install poetry-plugin-export
shell: bash

- if: ${{ runner.os != 'macOS' || runner.arch != 'ARM64' }}
run: |
pip install --user pipx
pipx ensurepath
pipx install poetry ${{ runner.os == 'macOS' && '--python "$Python_ROOT_DIR/bin/python"' || '' }}
pipx inject poetry poetry-plugin-export
shell: bash

- name: Get full Python version
Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/check-python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ jobs:
- name: Audit with pip-audit
run: |
# audit non dev dependencies, no exclusions
poetry export --without=dev > requirements.txt && poetry run pip-audit -r requirements.txt
poetry export --without=dev > requirements.txt
poetry run pip-audit -r requirements.txt --ignore-vuln 'GHSA-79v4-65xg-pq4g'
# audit all dependencies, with exclusions.
# If a vulnerability is found in a dev dependency without an available fix,
# it can be temporarily ignored by adding --ignore-vuln e.g.
poetry run pip-audit
# TODO: decide on `GHSA-79v4-65xg-pq4g`, see https://osv.dev/vulnerability/GHSA-79v4-65xg-pq4g
# The vulnerability is not applicable to the cli case, the only abstraciton leveraged is `RSAPublicKey` in
# vendored src/algokit/core/_vendor/auth0/authentication/token_verifier.py that was added to remove dependency
# on auth0 package that caused many adhoc transitive dependency errors in cli. As a result, consequent cryptography
# vulnerabilities need to be a) verified for applicability to cli case and ignored if not applicable or b) fixed by
# updating the vendored file to use the latest version of `cryptography` that has the fix.
- name: Check formatting with Ruff
run: |
Expand Down
209 changes: 73 additions & 136 deletions poetry.lock

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ tomli = { version = "^2.0.1", python = "<3.11" }
python-dotenv = "^1.0.0"
mslex = "^1.1.0"
keyring = "25.2.1"
pyjwt = "^2.8.0"
cryptography = "^43.0.1" # pyjwt has a weak dependency on cryptography
algokit-utils = "^2.3.0"
# pyjwt is locked to version ^2.8.0 because its explicitly
# vendored from auth0 repo, to reduce depedency on auth0 package that caused many adhoc transitive dependency errors in cli
# see header in src/algokit/core/_vendor/auth0/authentication/token_verifier.py
pyjwt = "^2.8.0"
cryptography = "^43.0.1" # pyjwt has a weak dependency on cryptography and explicitly requires it in the vendored file, hence the lock
algokit-utils = "^3.0.0"
multiformats = "0.3.1"
multiformats_config = "0.3.1" # pinned this to be in lockstep with multiformats
jsondiff = "^2.0.0"
Expand All @@ -44,6 +47,7 @@ sphinxnotes-markdown-builder = "^0.5.6"
poethepoet = ">=0.17.1,<0.27.0"
gfm-toc = "^0.0.7"
pytest-xdist = "^3.4.0"
pytest-sugar = "^1.0.0"

[build-system]
requires = ["poetry-core"]
Expand Down
1 change: 0 additions & 1 deletion src/algokit/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@

from algokit.cli import algokit

# Required to support full feature parity when running in binary execution mode
freeze_support()
algokit()
30 changes: 21 additions & 9 deletions src/algokit/cli/tasks/assets.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import logging

import click
from algokit_utils import opt_in, opt_out
from algosdk import error
from algosdk.v2client.algod import AlgodClient

Expand All @@ -14,6 +13,7 @@
validate_account_balance_to_opt_in,
validate_address,
)
from algokit.core.utils import get_algorand_client_for_network

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -55,19 +55,24 @@ def opt_in_command(asset_ids: tuple[int], account: str, network: AlgorandNetwork
opt_in_account = get_account_with_private_key(account)
validate_address(opt_in_account.address)
algod_client = load_algod_client(network)
algorand = get_algorand_client_for_network(network)

validate_account_balance_to_opt_in(algod_client, opt_in_account, len(asset_ids_list))
try:
click.echo("Performing opt-in. This may take a few seconds...")
response = opt_in(algod_client=algod_client, account=opt_in_account, asset_ids=asset_ids_list)
response = algorand.asset.bulk_opt_in(
account=opt_in_account.address,
asset_ids=asset_ids_list,
signer=opt_in_account.signer,
)
click.echo("Successfully performed opt-in.")
if len(response) > 1:
account_url = get_explorer_url(opt_in_account.address, network, ExplorerEntityType.ADDRESS)
click.echo(f"Check latest transactions on your account at: {account_url}")
else:
for asset_id, txn_id in response.items():
explorer_url = get_explorer_url(txn_id, network, ExplorerEntityType.ASSET)
click.echo(f"Check opt-in status for asset {asset_id} at: {explorer_url}")
for asset_opt_int_result in response:
explorer_url = get_explorer_url(asset_opt_int_result.transaction_id, network, ExplorerEntityType.ASSET)
click.echo(f"Check opt-in status for asset {asset_opt_int_result.asset_id} at: {explorer_url}")
except error.AlgodHTTPError as err:
raise click.ClickException(str(err)) from err
except ValueError as err:
Expand Down Expand Up @@ -106,6 +111,7 @@ def opt_out_command(*, asset_ids: tuple[int], account: str, network: AlgorandNet
opt_out_account = get_account_with_private_key(account)
validate_address(opt_out_account.address)
algod_client = load_algod_client(network)
algorand = get_algorand_client_for_network(network)
asset_ids_list = []
try:
asset_ids_list = _get_zero_balanced_assets(
Expand All @@ -119,15 +125,21 @@ def opt_out_command(*, asset_ids: tuple[int], account: str, network: AlgorandNet
raise click.ClickException("No assets to opt-out of.")

click.echo("Performing opt-out. This may take a few seconds...")
response = opt_out(algod_client=algod_client, account=opt_out_account, asset_ids=asset_ids_list)
response = algorand.asset.bulk_opt_out(
account=opt_out_account.address,
asset_ids=asset_ids_list,
signer=opt_out_account.signer,
)
click.echo("Successfully performed opt-out.")
if len(response) > 1:
account_url = get_explorer_url(opt_out_account.address, network, ExplorerEntityType.ADDRESS)
click.echo(f"Check latest transactions on your account at: {account_url}")
else:
asset_id, txn_id = response.popitem()
transaction_url = get_explorer_url(txn_id, network, ExplorerEntityType.TRANSACTION)
click.echo(f"Check opt-in status for asset {asset_id} at: {transaction_url}")
asset_opt_out_result = response[0]
transaction_url = get_explorer_url(
asset_opt_out_result.transaction_id, network, ExplorerEntityType.TRANSACTION
)
click.echo(f"Check opt-in status for asset {asset_opt_out_result.asset_id} at: {transaction_url}")
except error.AlgodHTTPError as err:
raise click.ClickException(str(err)) from err
except ConnectionRefusedError as err:
Expand Down
14 changes: 7 additions & 7 deletions src/algokit/cli/tasks/mint.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import json
import logging
import math
from decimal import Decimal
from pathlib import Path

import click
from algokit_utils import Account
from algokit_utils import AlgoAmount, SigningAccount
from algosdk.error import AlgodHTTPError
from algosdk.util import algos_to_microalgos

from algokit.cli.common.constants import AlgorandNetwork, ExplorerEntityType
from algokit.cli.common.utils import get_explorer_url
Expand All @@ -31,7 +31,7 @@

MAX_UNIT_NAME_BYTE_LENGTH = 8
MAX_ASSET_NAME_BYTE_LENGTH = 32
ASSET_MINTING_MBR = 0.2 # Algos, 0.1 for base account, 0.1 for asset creation
ASSET_MINTING_MBR = Decimal(0.2) # Algos, 0.1 for base account, 0.1 for asset creation


def _validate_supply(total: int, decimals: int) -> None:
Expand Down Expand Up @@ -115,7 +115,7 @@ def _get_and_validate_asset_name(context: click.Context, param: click.Parameter,
)


def _get_creator_account(_: click.Context, __: click.Parameter, value: str) -> Account:
def _get_creator_account(_: click.Context, __: click.Parameter, value: str) -> SigningAccount:
"""
Validate the creator account by checking if it is a valid Algorand address.
Expand All @@ -124,7 +124,7 @@ def _get_creator_account(_: click.Context, __: click.Parameter, value: str) -> A
value (str): The value of the parameter.
Returns:
Account: An account object with the address and private key.
SigningAccount: An account object with the address and private key.
"""
try:
return get_account_with_private_key(value)
Expand Down Expand Up @@ -284,7 +284,7 @@ def _validate_supply_for_nft(context: click.Context, _: click.Parameter, value:
)
def mint( # noqa: PLR0913
*,
creator: Account,
creator: SigningAccount,
asset_name: str,
unit_name: str,
total: int,
Expand All @@ -304,7 +304,7 @@ def mint( # noqa: PLR0913
client,
creator,
0,
algos_to_microalgos(ASSET_MINTING_MBR), # type: ignore[no-untyped-call]
AlgoAmount.from_algo(ASSET_MINTING_MBR).micro_algo,
)

token_metadata = TokenMetadata.from_json_file(token_metadata_path, asset_name, decimals)
Expand Down
56 changes: 28 additions & 28 deletions src/algokit/cli/tasks/transfer.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import logging
from typing import TYPE_CHECKING

import click
from algokit_utils import (
TransferAssetParameters,
TransferParameters,
transfer_asset,
)
from algokit_utils import (
transfer as transfer_algos,
)
from algokit_utils import AlgoAmount, AssetTransferParams, PaymentParams, SendAtomicTransactionComposerResults

from algokit.cli.common.constants import AlgorandNetwork, ExplorerEntityType
from algokit.cli.common.utils import get_explorer_url
Expand All @@ -21,9 +13,7 @@
validate_address,
validate_balance,
)

if TYPE_CHECKING:
from algosdk.transaction import AssetTransferTxn, PaymentTxn
from algokit.core.utils import get_algorand_client_for_network

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -95,29 +85,39 @@ def transfer( # noqa: PLR0913
validate_balance(algod_client, receiver_address, asset_id)

# Transfer algos or assets depending on asset_id
txn_response: PaymentTxn | AssetTransferTxn | None = None
txn_response: SendAtomicTransactionComposerResults | None = None
algorand = get_algorand_client_for_network(network)
try:
if asset_id == 0:
txn_response = transfer_algos(
algod_client,
TransferParameters(to_address=receiver_address, from_account=sender_account, micro_algos=amount),
txn_response = (
algorand.new_group()
.add_payment(
PaymentParams(
sender=sender_account.address,
receiver=receiver_address,
amount=AlgoAmount(micro_algo=amount),
signer=sender_account.signer,
)
)
.send()
)
else:
txn_response = transfer_asset(
algod_client,
TransferAssetParameters(
from_account=sender_account,
to_address=receiver_address,
amount=amount,
asset_id=asset_id,
),
txn_response = (
algorand.new_group()
.add_asset_transfer(
AssetTransferParams(
sender=sender_account.address,
receiver=receiver_address,
amount=amount,
asset_id=asset_id,
signer=sender_account.signer,
),
)
.send()
)

if not txn_response:
raise click.ClickException("Failed to perform transfer")

txn_url = get_explorer_url(
identifier=txn_response.get_txid(), # type: ignore[no-untyped-call]
identifier=txn_response.tx_ids[0],
network=network,
entity_type=ExplorerEntityType.TRANSACTION,
)
Expand Down
Loading

1 comment on commit 7344544

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
src/algokit
   __init__.py15753%6–13, 17–24, 32–34
   __main__.py440%1–6
src/algokit/cli
   __init__.py47394%31–34
   codespace.py50982%28, 114, 137, 150–155
   completions.py108992%63–64, 83, 93–99
   dispenser.py121199%77
   doctor.py53394%146–148
   explore.py631576%35–40, 42–47, 85–92, 113
   generate.py60395%76–77, 155
   goal.py54689%67, 85, 96, 107–109
   init.py3082492%485–486, 491–492, 495, 516, 519–521, 532, 536, 594, 620, 649, 682, 691–693, 696–701, 714, 733, 745–746
   localnet.py1643280%67, 88–115, 164–168, 212, 233, 248–258, 271, 322, 343–344
   task.py34391%25–28
src/algokit/cli/common
   utils.py37295%137, 139
src/algokit/cli/project
   bootstrap.py33197%33
   deploy.py1172083%47, 49, 102, 125, 147–149, 270, 277, 291–299, 302–311
   link.py811285%60, 65–66, 109–114, 142–143, 212–213, 217
   list.py33585%21–23, 51–56
   run.py46296%38, 174
src/algokit/cli/tasks
   analyze.py81199%81
   assets.py841385%70–71, 77, 79–80, 110, 125, 135–136, 144, 146, 148–149
   ipfs.py51884%52, 80, 92, 94–95, 105–107
   mint.py1061586%51, 73, 100–103, 108, 113, 131–132, 158, 335–339
   send_transaction.py651085%52–53, 57, 89, 158, 170–174
   sign_transaction.py59886%21, 28–30, 71–72, 109, 123
   transfer.py35197%80
   utils.py1144660%24–32, 38–41, 73–74, 98–99, 123–131, 150–160, 207, 256–257, 277–288, 295–297, 319
   vanity_address.py561082%41, 45–48, 112, 114, 121–123
   wallet.py79495%21, 66, 136, 162
src/algokit/core
   codespace.py1756861%34–37, 41–44, 48–71, 111–112, 125–133, 191, 200–202, 210, 216–217, 229–236, 251–298, 311–313, 338–344, 348, 395
   conf.py57984%12, 24, 28, 36, 38, 73–75, 80
   dispenser.py2022687%92, 124–125, 142–150, 192–193, 199–201, 219–220, 260–261, 319, 333–335, 346–347, 357, 370, 385
   doctor.py65789%67–69, 92–94, 134
   generate.py50394%44, 85, 103
   goal.py65494%21, 36–37, 47
   init.py721086%53, 57–62, 70, 81, 88, 114–115
   log_handlers.py68790%50–51, 63, 112–116, 125
   proc.py45198%99
   sandbox.py2782392%32, 89–92, 97, 101–103, 176, 224–231, 242, 613, 629, 654, 662
   typed_client_generation.py2062190%79–81, 127, 157–162, 186, 189–192, 210, 213–216, 283, 286–289
   utils.py1675269%25–27, 58–59, 65–77, 133–139, 163, 166, 172–185, 214–216, 245–248, 270, 290–300
src/algokit/core/_vendor/auth0/authentication
   token_verifier.py15711129%16, 45, 58, 73–85, 98–107, 119–124, 136–137, 140, 170, 178–180, 190–199, 206–213, 227–236, 258, 280–287, 314–323, 333–444
src/algokit/core/compilers
   python.py28582%19–20, 25, 49–50
src/algokit/core/config_commands
   container_engine.py412149%24, 29–31, 47–76
   version_prompt.py921485%37–38, 68, 87–90, 108, 118–125, 148
src/algokit/core/project
   __init__.py53394%50, 86, 145
   bootstrap.py125894%47, 126–127, 149, 176, 216–218
   deploy.py69987%108–111, 120–122, 126, 131
   run.py1321390%83, 88, 97–98, 133–134, 138–139, 143, 147, 277–278, 293
src/algokit/core/tasks
   analyze.py93397%105–112, 187
   ipfs.py63789%58–64, 140, 144, 146, 152
   nfd.py491373%25, 31, 34–41, 70–72, 99–101
   vanity_address.py903462%49–50, 54, 59–75, 92–108, 128–131
   wallet.py71593%37, 129, 155–157
src/algokit/core/tasks/mint
   mint.py74988%123–133
   models.py921782%50, 52, 57, 71–74, 81–90
TOTAL498978084% 

Tests Skipped Failures Errors Time
516 0 💤 0 ❌ 0 🔥 25.847s ⏱️

Please sign in to comment.