Package: algo-app-dev
Algo App DevUtilities to help in the development of PyTeal contracts for Algorand.
Documentation:https://gmcgoldr.github.io/algo-app-dev/.Installationpipinstall-Ualgo-app-devPre-requisitsIn this documentation, it is assumed that you are running an Algorand node in an Ubuntu environment.You can install Algorand with following these commands:sudoapt-getupdate
sudoapt-getinstall-ygnupg2curlsoftware-properties-common
curl-Ohttps://releases.algorand.com/key.pub
sudoapt-keyaddkey.pub
rm-fkey.pub
sudoadd-apt-repository"deb [arch=amd64] https://releases.algorand.com/deb/ stable main"sudoapt-getupdate
sudoapt-getinstallalgorandCommand line utilitiesThe following command line utilities are installed with the package.
They are used to work with a local private network and node daemons for development:aad-make-node: this command can be used to setup a private networkaad-run-node: this command can be used to start or stop node daemonsModulesThe following is a brief overview of the package's functionality and organization:clientsTheclientsmodule contains a few utilities to help manage thealgodandkmddaemons clients.There are also utilities to help extract key-value information from global and local state queries.transactionsThetransactionsmodule contains utilities to help create and manage transactions.appsTheappsmodule contains utilities and classes to help construct and manage stateful applications (stateful smart contracts).
This is the core of the package.Most the app development work utilizes two classes: theStateandAppBuilderclasses.
These help reduce the amount of boiler-plate needed to create functionalpytealexpressions.Manually managing the app's state is very error prone.
The interface provided byStateand its derivedStateGlobal,StateGlobalExternal,StateLocalandStateLocalExternalcan reduce these errors.Here is an example of a very simple app with a global counter.
Every time a (no-op) call is made with the argument "count", it increments the counter.importpytealastlfromalgosdk.utilimportalgos_to_microalgosfromalgoappdevimportapps,clients,dryruns,transactions,utils# build the clientsalgod_client=clients.build_algod_local_client(NODE_PATH)kmd_client=clients.build_kmd_local_client(NODE_PATH)# fund an account on the private net which can be used to transactfunded_account,txid=transactions.fund_from_genesis(algod_client,kmd_client,algos_to_microalgos(1))# wait for the funding to go throughtransactions.get_confirmed_transaction(algod_client,txid,WAIT_ROUNDS)# define the state: a single global counter which defaults to 0state=apps.StateGlobal([apps.State.KeyInfo("counter",tl.Int,tl.Int(0))])# define the logic: invoking with the argument "count" increments the counterapp_builder=apps.AppBuilder(invocations={"count":tl.Seq(state.set("counter",state.get("counter")+tl.Int(1)),tl.Return(tl.Int(1)),),},global_state=state,)# deploy the applicationtxn=app_builder.create_txn(algod_client,funded_account.address,algod_client.suggested_params())txid=algod_client.send_transaction(txn.sign(funded_account.key))# the app id and address can be derived from the transaction outputtxn_info=transactions.get_confirmed_transaction(algod_client,txid,WAIT_ROUNDS)app_meta=utils.AppMeta.from_result(txn_info)print(app_meta)The resultingapp_metaobject:AppMeta(app_id=1,address='...')dryrunsThedryrunsmodule contains utilities to help send dry runs to a node,
and parse the results.Here is how thedryrunsutilities could be used to test the contract:# build a dryrun request containing the entire state needed to call the appresult=algod_client.dryrun(dryruns.AppCallCtx()# use the app's programs and schema.with_app(app_builder.build_application(algod_client,1))# add a transaction calling the app with the given arg.with_txn_call(args=[b"count"]).build_request())foritemindryruns.get_trace(result):print(item)fordeltaindryruns.get_global_deltas(result):print(delta)The last few lines of the stack trace should resemble:55 :: app_global_get :: [b'counter'  , 0           ]
56 :: intc_0 // 1    :: [b'counter'  , 0           , 1           ]
57 :: +              :: [b'counter'  , 1           ]
58 :: app_global_put :: []
59 :: intc_0 // 1    :: [1           ]
81 :: return         :: [1           ]The resulting state delta:KeyDelta(key=b'counter',value=1)TestingNOTE: in order to use the testing functionality, you must install thedevdependencies.
This is done with the command:pipinstall-Ualgo-app-dev[dev]Start the daemons before testing, and optionally stop them after the tests run.The tests make calls to the node, which is slow. There are two mitigations for this:
using the dev node, and using thepytest-xdistplugin for pytest to parallelize the test.The dev node creates a new block for every transaction, meaning that there is no need to wait for consensus.
Whereas testing withprivate_devcan take a 10s of seconds,
testing withpivatetakes 10s of minutes.The flag-n Xcan be used to split the tests into that many parallel processes.# create a new network (overwrites an existing private dev node)aad-make-nodenets/private_dev-f# start the node daemonsaad-run-nodenets/private_devstart# run the tests in 4 processes with the given node dirAAD_NODE_DIR=nets/private_dev/Primarypytest-n4tests/# stop the node daemonsaad-run-nodenets/private_devstopPyTest environmentThe modulealgoappdev.testingcontains somepytestfixtures that are widely applicable.
If you want to make those fixtures available to all your tests,
you can create a fileconftest.pyin your test root directory and write to it:# conftest.pyfromalgoappdev.testingimport*It also exposes two variables which can be configured through environment variables:NODE_DIR: this should be the path to the node data directoryWAIT_ROUNDS: this should be set to the maximum number of rounds to await transaction confirmations.Both are read from the environment variable with corresponding name prefixed withAAD_.NODE_DIRdefaults to the private dev node data path.
If your system is configured differently, you will need to set this accordingly.WAIT_ROUNDSdefaults to 1, because when interacting with a dev node transactions are immediately committed.
If doing integration tests with a non-dev node,
this should be increased to give time for transactions to complete before moving onto another test.