Skip to content

Commit

Permalink
feat: adding support for interacting with dispenser api on testnet vi…
Browse files Browse the repository at this point in the history
…a ensure_funded; api client class (#51)

* feat: adding support for interacting with dispenser api on testnet

* chore: adding pr template

* chore: simplifying setup; let clients handle error codes as they see fit

* chore: replacing deprecated recommended plugin

* feat: adding dispenser api client; refining ensure_funded

BREAKING CHANGE: Adding new client class for interacting with TestNet Dispenser API; Changing output type of ensure_funded method

* docs: updating documentation fixing urls

* chore: addressing pr comments

* chore: fixing pip audit warning
  • Loading branch information
aorumbayev authored Oct 3, 2023
1 parent c8401e8 commit 4f1f057
Show file tree
Hide file tree
Showing 31 changed files with 1,224 additions and 202 deletions.
5 changes: 5 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Proposed Changes

-
-
-
22 changes: 11 additions & 11 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"ms-python.python",
"ms-python.vscode-pylance",
"charliermarsh.ruff",
"bungcip.better-toml",
"editorconfig.editorconfig",
"emeraldwalk.runonsave",
"matangover.mypy",
"runarsf.platform-settings"
]
"recommendations": [
"esbenp.prettier-vscode",
"ms-python.python",
"ms-python.vscode-pylance",
"charliermarsh.ruff",
"tamasfe.even-better-toml",
"editorconfig.editorconfig",
"emeraldwalk.runonsave",
"matangover.mypy",
"runarsf.platform-settings"
]
}
80 changes: 77 additions & 3 deletions docs/html/_sources/apidocs/algokit_utils/algokit_utils.md.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,21 @@ orphan: true
:parser: myst
:summary:
```
* - {py:obj}`DispenserApiTestnetClient <algokit_utils.dispenser_api.DispenserApiTestnetClient>`
- ```{autodoc2-docstring} algokit_utils.dispenser_api.DispenserApiTestnetClient
:parser: myst
:summary:
```
* - {py:obj}`EnsureBalanceParameters <algokit_utils._ensure_funded.EnsureBalanceParameters>`
- ```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureBalanceParameters
:parser: myst
:summary:
```
* - {py:obj}`EnsureFundedResponse <algokit_utils._ensure_funded.EnsureFundedResponse>`
- ```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureFundedResponse
:parser: myst
:summary:
```
* - {py:obj}`MethodHints <algokit_utils.application_specification.MethodHints>`
- ```{autodoc2-docstring} algokit_utils.application_specification.MethodHints
:parser: myst
Expand Down Expand Up @@ -1110,6 +1120,49 @@ Bases: {py:obj}`Exception`

```

`````{py:class} DispenserApiTestnetClient(auth_token: str | None = None, request_timeout: int = DISPENSER_REQUEST_TIMEOUT)
:canonical: algokit_utils.dispenser_api.DispenserApiTestnetClient

```{autodoc2-docstring} algokit_utils.dispenser_api.DispenserApiTestnetClient
:parser: myst
```

```{rubric} Initialization
```

```{autodoc2-docstring} algokit_utils.dispenser_api.DispenserApiTestnetClient.__init__
:parser: myst
```

````{py:method} fund(address: str, amount: int, asset_id: int) -> algokit_utils.dispenser_api.DispenserFundResponse
:canonical: algokit_utils.dispenser_api.DispenserApiTestnetClient.fund

```{autodoc2-docstring} algokit_utils.dispenser_api.DispenserApiTestnetClient.fund
:parser: myst
```

````

````{py:method} limit(address: str) -> algokit_utils.dispenser_api.DispenserLimitResponse
:canonical: algokit_utils.dispenser_api.DispenserApiTestnetClient.limit

```{autodoc2-docstring} algokit_utils.dispenser_api.DispenserApiTestnetClient.limit
:parser: myst
```

````

````{py:method} refund(refund_txn_id: str) -> None
:canonical: algokit_utils.dispenser_api.DispenserApiTestnetClient.refund

```{autodoc2-docstring} algokit_utils.dispenser_api.DispenserApiTestnetClient.refund
:parser: myst
```

````

`````

`````{py:class} EnsureBalanceParameters
:canonical: algokit_utils._ensure_funded.EnsureBalanceParameters

Expand Down Expand Up @@ -1143,7 +1196,7 @@ Bases: {py:obj}`Exception`

````{py:attribute} funding_source
:canonical: algokit_utils._ensure_funded.EnsureBalanceParameters.funding_source
:type: algokit_utils.models.Account | algosdk.atomic_transaction_composer.AccountTransactionSigner | None
:type: algokit_utils.models.Account | algosdk.atomic_transaction_composer.AccountTransactionSigner | algokit_utils.dispenser_api.DispenserApiTestnetClient | None
:value: >
None

Expand Down Expand Up @@ -1215,7 +1268,28 @@ Bases: {py:obj}`Exception`

`````

```{py:exception} LogicError(*, logic_error_str: str, program: str, source_map: AlgoSourceMap | None, transaction_id: str, message: str, pc: int, logic_error: Exception | None = None)
`````{py:class} EnsureFundedResponse
:canonical: algokit_utils._ensure_funded.EnsureFundedResponse

```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureFundedResponse
:parser: myst
```

````{py:attribute} transaction_id
:canonical: algokit_utils._ensure_funded.EnsureFundedResponse.transaction_id
:type: str
:value: >
None

```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureFundedResponse.transaction_id
:parser: myst
```

````

`````

```{py:exception} LogicError(*, logic_error_str: str, program: str, source_map: AlgoSourceMap | None, transaction_id: str, message: str, pc: int, logic_error: Exception | None = None, traces: list | None = None)
:canonical: algokit_utils.logic_error.LogicError

Bases: {py:obj}`Exception`
Expand Down Expand Up @@ -1723,7 +1797,7 @@ Bases: {py:obj}`algokit_utils._transfer.TransferParametersBase`
```
````

````{py:function} ensure_funded(client: algosdk.v2client.algod.AlgodClient, parameters: algokit_utils._ensure_funded.EnsureBalanceParameters) -> None | algosdk.transaction.PaymentTxn
````{py:function} ensure_funded(client: algosdk.v2client.algod.AlgodClient, parameters: algokit_utils._ensure_funded.EnsureBalanceParameters) -> algokit_utils._ensure_funded.EnsureFundedResponse | None
:canonical: algokit_utils._ensure_funded.ensure_funded

```{autodoc2-docstring} algokit_utils._ensure_funded.ensure_funded
Expand Down
8 changes: 8 additions & 0 deletions docs/html/_sources/capabilities/app-client.md.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ When an error is thrown then the resulting error that is re-thrown will be a `Lo
* `transaction_id`: Transaction ID of failing transaction
* `message`: The error message
* `line_no`: The line number in the TEAL program that
* `traces`: A list of Trace objects providing additional insights on simulation when debug mode is active.

The function `trace()` will provide a formatted output of the surrounding TEAL where the error occurred.

Expand All @@ -147,3 +148,10 @@ The extended information will only show if the Application Client has a source m
3.) `approval_source_map` on `ApplicationClient` has been set from a previously compiled approval program OR
4.) A source map has been exported/imported using `export_source_map`/`import_source_map`"""
```

### Debug Mode and traces Field
When debug mode is active, the LogicError will contain a field named traces. This field will include raw simulate execution traces, providing a detailed account of the transaction simulation. These traces are crucial for diagnosing complex issues and are automatically included in all application client calls when debug mode is active.

```{note}
Remember to enable debug mode (`config.debug = True`) to include raw simulate execution traces in the `LogicError`.
```
72 changes: 37 additions & 35 deletions docs/html/_sources/capabilities/app-deploy.md.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

Idempotent (safely retryable) deployment of an app, including deploy-time immutability and permanence control and TEAL template substitution

App deployment is a higher-order use case capability provided by AlgoKit Utils that builds on top of the core capabilities,
particularly [App management](./app-client.md). It allows you to idempotently (with safe retryability) deploy an app, including deploy-time immutability and permanence control and
App deployment is a higher-order use case capability provided by AlgoKit Utils that builds on top of the core capabilities,
particularly [App management](./app-client.md). It allows you to idempotently (with safe retryability) deploy an app, including deploy-time immutability and permanence control and
TEAL template substitution.

To see some usage examples check out the [automated tests](https://github.com/algorandfoundation/algokit-utils-py/blob/main/tests/test_deploy_scenarios.py).

(design)=

## Design

The architecture design behind app deployment is articulated in an [architecture decision record](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/architecture-decisions/2023-01-12_smart-contract-deployment.md).
The architecture design behind app deployment is articulated in an [architecture decision record](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/architecture-decisions/2023-01-12_smart-contract-deployment.md).
While the implementation will naturally evolve over time and diverge from this record, the principles and design goals behind the design are comprehensively explained.

Namely, it described the concept of a smart contract development lifecycle:
Expand All @@ -35,17 +36,17 @@ The App deployment capability provided by AlgoKit Utils helps implement **#2 Dep
Furthermore, the implementation contains the following implementation characteristics per the original architecture design:

- Deploy-time parameters can be provided and substituted into a TEAL Template by convention (by replacing `TMPL_{KEY}`)
- Contracts can be built by any smart contract framework that supports [ARC-0032](https://arc.algorand.foundation/ARCs/arc-0032) and
[ARC-0004](https://arc.algorand.foundation/ARCs/arc-0004) ([Beaker](https://beaker.algo.xyz/) or otherwise), which also means the deployment language can be
- Contracts can be built by any smart contract framework that supports [ARC-0032](https://arc.algorand.foundation/ARCs/arc-0032) and
[ARC-0004](https://arc.algorand.foundation/ARCs/arc-0004) ([Beaker](https://beaker.algo.xyz/) or otherwise), which also means the deployment language can be
different to the development language e.g. you can deploy a Python smart contract with TypeScript for instance
- There is explicit control of the immutability (updatability / upgradeability) and permanence (deletability) of the smart contract, which can be varied per environment to allow for easier
- There is explicit control of the immutability (updatability / upgradeability) and permanence (deletability) of the smart contract, which can be varied per environment to allow for easier
development and testing in non-MainNet environments (by replacing `TMPL_UPDATABLE` and `TMPL_DELETABLE` at deploy-time by convention, if present)
- Contracts are resolvable by a string "name" for a given creator to allow automated determination of whether that contract had been deployed previously or not, but can also be resolved by ID
- Contracts are resolvable by a string "name" for a given creator to allow automated determination of whether that contract had been deployed previously or not, but can also be resolved by ID
instead

## Finding apps by creator

There is a method `algokit.get_creator_apps(creatorAccount, indexer)`, which performs a series of indexer lookups that return all apps created by the given creator. These are indexed by the name it
There is a method `algokit.get_creator_apps(creatorAccount, indexer)`, which performs a series of indexer lookups that return all apps created by the given creator. These are indexed by the name it
was deployed under if the creation transaction contained the following payload in the transaction note field:

```
Expand All @@ -54,20 +55,20 @@ ALGOKIT_DEPLOYER:j{name:string, version:string, updatable?:boolean, deletable?:b

Any creation transactions or update transactions are then retrieved and processed in chronological order to result in an `AppLookup` object

Given there are a number of indexer calls to retrieve this data it's a non-trivial object to create, and it's recommended that for the duration you are performing a single deployment
you hold a value of it rather than recalculating it. Most AlgoKit Utils functions that need it will also take an optional value of it that will be used in preference to retrieving a
Given there are a number of indexer calls to retrieve this data it's a non-trivial object to create, and it's recommended that for the duration you are performing a single deployment
you hold a value of it rather than recalculating it. Most AlgoKit Utils functions that need it will also take an optional value of it that will be used in preference to retrieving a
fresh version.

## Deploying an application

The method that performs the deployment logic is the instance method `ApplicationClient.deploy`. It performs an idempotent (safely retryable) deployment. It will detect if the app already
The method that performs the deployment logic is the instance method `ApplicationClient.deploy`. It performs an idempotent (safely retryable) deployment. It will detect if the app already
exists and if it doesn't it will create it. If the app does already exist then it will:

- Detect if the app has been updated (i.e. the logic has changed) and either fail or perform either an update or a replacement based on the deployment configuration.
- Detect if the app has a breaking schema change (i.e. more global or local storage is needed than was originally requested) and either fail or perform a replacement based on the
- Detect if the app has a breaking schema change (i.e. more global or local storage is needed than was originally requested) and either fail or perform a replacement based on the
deployment configuration.

It will automatically add metadata to the transaction note of the create or update calls that indicates the name, version, updatability and deletability of the contract.
It will automatically add metadata to the transaction note of the create or update calls that indicates the name, version, updatability and deletability of the contract.
This metadata works in concert with `get_creator_apps` to allow the app to be reliably retrieved against that creator in it's currently deployed state.

`deploy` automatically executes [template substitution](#compilation-and-template-substitution) including deploy-time control of permanence and immutability.
Expand All @@ -76,44 +77,45 @@ This metadata works in concert with `get_creator_apps` to allow the app to be re

The following inputs are used when deploying an App

* `version`: The version string for the app defined in app_spec, if not specified the version will automatically increment for existing apps that are updated, and set to 1.0 for new apps
* `signer`, `sender`: Optional signer and sender for deployment operations, sender must be the same as the creator specified
* `allow_update`, `allow_delete`: Control the updatability and deletability of the app, used to populate `TMPL_UPDATABLE` and `TMPL_DELETABLE` template values
* `on_update`: Determines what should happen if an update to the smart contract is detected (e.g. the TEAL code has changed since last deployment)
* `on_schema_break`: Determines what should happen if a breaking change to the schema is detected (e.g. if you need more global or local state that was previously requested when the contract was originally created)
* `create_args`: Args to use if a create operation is performed
* `update_args`: Args to use if an update operation is performed
* `delete_args`: Args to use if a delete operation is performed
* `template_values`: Values to use for automatic substitution of [deploy-time parameter values](#design) is mapping of `key: value` that will result in `TMPL_{key}` being replaced with `value`
- `version`: The version string for the app defined in app_spec, if not specified the version will automatically increment for existing apps that are updated, and set to 1.0 for new apps
- `signer`, `sender`: Optional signer and sender for deployment operations, sender must be the same as the creator specified
- `allow_update`, `allow_delete`: Control the updatability and deletability of the app, used to populate `TMPL_UPDATABLE` and `TMPL_DELETABLE` template values
- `on_update`: Determines what should happen if an update to the smart contract is detected (e.g. the TEAL code has changed since last deployment)
- `on_schema_break`: Determines what should happen if a breaking change to the schema is detected (e.g. if you need more global or local state that was previously requested when the contract was originally created)
- `create_args`: Args to use if a create operation is performed
- `update_args`: Args to use if an update operation is performed
- `delete_args`: Args to use if a delete operation is performed
- `template_values`: Values to use for automatic substitution of [deploy-time parameter values](#design) is mapping of `key: value` that will result in `TMPL_{key}` being replaced with `value`

### Idempotency

`deploy` is idempotent which means you can safely call it again multiple times, and it will only apply any changes it detects. If you call it again straight after calling it then it will
`deploy` is idempotent which means you can safely call it again multiple times, and it will only apply any changes it detects. If you call it again straight after calling it then it will
do nothing. This also means it can be used to find an existing app based on the supplied creator and app_spec or name.

(compilation-and-template-substitution)=

### Compilation and template substitution

When compiling TEAL template code, the capabilities described in the [design above](#design) are present, namely the ability to supply deploy-time parameters and the ability to control immutability and permanence of the smart contract at deploy-time.

In order for a smart contract to be able to use this functionality, it must have a TEAL Template that contains the following:

* `TMPL_{key}` - Which can be replaced with a number or a string / byte array which wil be automatically hexadecimal encoded
* `TMPL_UPDATABLE` - Which will be replaced with a `1` if an app should be updatable and `0` if it shouldn't (immutable)
* `TMPL_DELETABLE` - Which will be replaced with a `1` if an app should be deletable and `0` if it shouldn't (permanent)
- `TMPL_{key}` - Which can be replaced with a number or a string / byte array which wil be automatically hexadecimal encoded
- `TMPL_UPDATABLE` - Which will be replaced with a `1` if an app should be updatable and `0` if it shouldn't (immutable)
- `TMPL_DELETABLE` - Which will be replaced with a `1` if an app should be deletable and `0` if it shouldn't (permanent)

If you are building a smart contract using the [beaker_production AlgoKit template](https://github.com/algorandfoundation/algokit-beaker-default-template) if provides a reference implementation out of the box for the deploy-time immutability and permanence control.

### Return value

`deploy` returns a `DeployResponse` object, that describes the action taken.

* `action_taken`: Describes what happened during deployment
* `Create` - The smart contract app is created.
* `Update` - The smart contract app is updated
* `Replace` - The smart contract app was deleted and created again (in an atomic transaction)
* `Nothing` - Nothing was done since an existing up-to-date app was found
* `create_response`: If action taken was `Create` or `Replace`, the result of the create transaction. Can be a `TransactionResponse` or `ABITransactionResponse` depending on the method used
* `update_response`: If action taken was `Update`, the result of the update transaction. Can be a `TransactionResponse` or `ABITransactionResponse` depending on the method used
* `delete_response`: If action taken was `Replace`, the result of the delete transaction. Can be a `TransactionResponse` or `ABITransactionResponse` depending on the method used
* `app`: An `AppMetaData` object, describing the final app state
- `action_taken`: Describes what happened during deployment
- `Create` - The smart contract app is created.
- `Update` - The smart contract app is updated
- `Replace` - The smart contract app was deleted and created again (in an atomic transaction)
- `Nothing` - Nothing was done since an existing up-to-date app was found
- `create_response`: If action taken was `Create` or `Replace`, the result of the create transaction. Can be a `TransactionResponse` or `ABITransactionResponse` depending on the method used
- `update_response`: If action taken was `Update`, the result of the update transaction. Can be a `TransactionResponse` or `ABITransactionResponse` depending on the method used
- `delete_response`: If action taken was `Replace`, the result of the delete transaction. Can be a `TransactionResponse` or `ABITransactionResponse` depending on the method used
- `app`: An `AppMetaData` object, describing the final app state
Loading

1 comment on commit 4f1f057

@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_utils
   _ensure_funded.py69199%99
   _transfer.py62395%13, 76–77
   account.py851385%14–17, 61–65, 96, 109, 136, 139, 183
   application_client.py5519683%53–54, 111, 128, 179, 184, 213, 325, 330–331, 333, 335, 402, 411, 420, 470, 478, 487, 531, 539, 548, 592, 600, 609, 661, 669, 678, 720, 728, 737, 797, 812, 830–833, 909, 949, 961, 974, 1016, 1076–1082, 1086–1091, 1093, 1129, 1136, 1247, 1279, 1333–1335, 1337, 1347–1404, 1415–1420, 1440–1443
   application_specification.py971189%92, 94, 193–202, 206
   config.py17759%13–18, 21–22
   deploy.py4552395%30–33, 168, 172–173, 190, 246, 402, 413–421, 438–441, 451, 459, 652–653, 677
   dispenser_api.py821285%112–113, 117–120, 155–157, 176–178
   logic_error.py38295%6, 30
   models.py126894%45, 50–52, 57, 61–62, 125
   network_clients.py66395%89–90, 121
TOTAL165917989% 

Tests Skipped Failures Errors Time
183 0 💤 0 ❌ 0 🔥 2m 7s ⏱️

Please sign in to comment.