diff --git a/.test-env b/.test-env new file mode 100644 index 00000000..3b17bb19 --- /dev/null +++ b/.test-env @@ -0,0 +1,14 @@ +# Configs for testing repo download: +SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" +SDK_TESTING_BRANCH="master" +SDK_TESTING_HARNESS="test-harness" + +VERBOSE_HARNESS=0 + +# WARNING: If set to 1, new features will be LOST when downloading the test harness. +# REGARDLESS: modified features are ALWAYS overwritten. +REMOVE_LOCAL_FEATURES=0 + +# WARNING: Be careful when turning on the next variable. +# In that case you'll need to provide all variables expected by `algorand-sdk-testing`'s `.env` +OVERWRITE_TESTING_ENVIRONMENT=0 diff --git a/CHANGELOG.md b/CHANGELOG.md index a408da63..1c9087eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## What's Changed +### Bugfixes +* Bug-fix: Pass verbosity through to testing harness by @tzaffi in https://github.com/algorand/py-algorand-sdk/pull/373 +### Enhancements +* Enhancement: Trim the indexer images and use the sandbox instead of custom dockers by @tzaffi in https://github.com/algorand/py-algorand-sdk/pull/367 +* Enhancement: Add State Proof support by @shiqizng in https://github.com/algorand/py-algorand-sdk/pull/370 +* Enhancement: Deprecating use of langspec by @ahangsu in https://github.com/algorand/py-algorand-sdk/pull/371 + +## New Contributors +* @ahangsu made their first contribution in https://github.com/algorand/py-algorand-sdk/pull/371 + +**Full Changelog**: https://github.com/algorand/py-algorand-sdk/compare/v1.16.1...v1.17.0 + +# v1.17.0b1 +## What's Changed +### Bugfixes +* Bug-fix: Pass verbosity through to testing harness by @tzaffi in https://github.com/algorand/py-algorand-sdk/pull/373 +### Enhancements +* Enhancement: Trim the indexer images and use the sandbox instead of custom dockers by @tzaffi in https://github.com/algorand/py-algorand-sdk/pull/367 +* Enhancement: Add State Proof support by @shiqizng in https://github.com/algorand/py-algorand-sdk/pull/370 +* Enhancement: Deprecating use of langspec by @ahangsu in https://github.com/algorand/py-algorand-sdk/pull/371 + +## New Contributors +* @ahangsu made their first contribution in https://github.com/algorand/py-algorand-sdk/pull/371 + +**Full Changelog**: https://github.com/algorand/py-algorand-sdk/compare/v1.16.1...v1.17.0b1 + + # v1.16.1 ### Bugfixes * bug-fix: add check to desc so we dont output null if undefined by @barnjamin in https://github.com/algorand/py-algorand-sdk/pull/368 diff --git a/Dockerfile b/Dockerfile index e6c1f178..f8fae3d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,5 +11,4 @@ RUN pip install . -q \ && pip install -r requirements.txt -q # Run integration tests -CMD ["/bin/bash", "-c", "make unit && make integration"] - +CMD ["/bin/bash", "-c", "python --version && make unit && make integration"] diff --git a/Makefile b/Makefile index ba7b6363..8b51618a 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,24 @@ -UNITS = "@unit.abijson or @unit.abijson.byname or @unit.algod or @unit.algod.ledger_refactoring or @unit.applications or @unit.atc_method_args or @unit.atomic_transaction_composer or @unit.dryrun or @unit.dryrun.trace.application or @unit.feetest or @unit.indexer or @unit.indexer.ledger_refactoring or @unit.indexer.logs or @unit.offline or @unit.rekey or @unit.transactions.keyreg or @unit.responses or @unit.responses.231 or @unit.tealsign or @unit.transactions or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.sourcemap" +UNIT_TAGS := "$(subst :, or ,$(shell awk '{print $2}' tests/unit.tags | paste -s -d: -))" +INTEGRATION_TAGS := "$(subst :, or ,$(shell awk '{print $2}' tests/integration.tags | paste -s -d: -))" + unit: - behave --tags=$(UNITS) tests -f progress2 + behave --tags=$(UNIT_TAGS) tests -f progress2 -INTEGRATIONS = "@abi or @algod or @applications or @applications.verified or @assets or @auction or @c2c or @compile or @dryrun or @dryrun.testing or @indexer or @indexer.231 or @indexer.applications or @kmd or @rekey_v1 or @send.keyregtxn or @send or @compile.sourcemap" integration: - behave --tags=$(INTEGRATIONS) tests -f progress2 + behave --tags=$(INTEGRATION_TAGS) tests -f progress2 --no-capture + +display-all-python-steps: + find tests/steps -name "*.py" | xargs grep "behave" 2>/dev/null | cut -d: -f1 | sort | uniq | xargs awk "/@(given|step|then|when)/,/[)]/" | grep -E "(\".+\"|\'.+\')" + +harness: + ./test-harness.sh PYTHON_VERSION ?= 3.8 -docker-test: - PYTHON_VERSION='$(PYTHON_VERSION)' ./run_integration.sh +docker-pysdk-build: + docker build -t py-sdk-testing --build-arg PYTHON_VERSION="${PYTHON_VERSION}" . + +docker-pysdk-run: + docker ps -a + docker run -it --network host py-sdk-testing:latest + +docker-test: harness docker-pysdk-build docker-pysdk-run diff --git a/README.md b/README.md index f8b8417d..ad526303 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,9 @@ Run `$ pip3 install py-algorand-sdk` to install the package. Alternatively, choose a [distribution file](https://pypi.org/project/py-algorand-sdk/#files), and run `$ pip3 install [file name]`. ## Supported Python versions + py-algorand-sdk's minimum Python version policy attempts to balance several constraints. + * Make it easy for the community to use py-algorand-sdk by minimizing or excluding the need to customize Python installations. * Provide maintainers with access to newer language features that produce more robust software. @@ -22,6 +24,7 @@ Given these constraints, the minimum Python version policy is: Target Python version on newest [Ubuntu LTS](https://wiki.ubuntu.com/Releases) released >= 6 months ago. The rationale is: + * If a major Linux OS distribution bumps a Python version, then it's sufficiently available to the community for us to upgrade. * The 6 month time buffer ensures we delay upgrades until the community starts using a recently released LTS version. @@ -29,15 +32,19 @@ The rationale is: Install dependencies -- `pip3 install -r requirements.txt` +* `pip3 install -r requirements.txt` Run tests -- `make docker-test` +* `make docker-test` + +Set up the Algorand Sandbox based test-harness without running the tests + +* `make harness` Format code: -- `black .` +* `black .` ## Quick start @@ -61,6 +68,7 @@ else: ## Node setup Follow the instructions in Algorand's [developer resources](https://developer.algorand.org/docs/run-a-node/setup/install/) to install a node on your computer. +You can also set up a local [Algorand Sandbox](https://github.com/algorand/sandbox) with `make harness`. ## Running examples/example.py diff --git a/algosdk/constants.py b/algosdk/constants.py index 6ea9fb52..6ddba7d4 100644 --- a/algosdk/constants.py +++ b/algosdk/constants.py @@ -26,6 +26,8 @@ """str: indicates an asset transfer transaction""" APPCALL_TXN = "appl" """str: indicates an app call transaction, allows creating, deleting, and interacting with an application""" +STATEPROOF_TXN = "stpf" +"""str: indicates an state proof transaction""" # note field types NOTE_FIELD_TYPE_DEPOSIT = "d" @@ -134,3 +136,4 @@ logic_sig_max_cost = LOGIC_SIG_MAX_COST logic_sig_max_size = LOGIC_SIG_MAX_SIZE app_page_max_size = APP_PAGE_MAX_SIZE +stateproof_txn = STATEPROOF_TXN diff --git a/algosdk/future/transaction.py b/algosdk/future/transaction.py index 6c6da6e9..c26c0cc6 100644 --- a/algosdk/future/transaction.py +++ b/algosdk/future/transaction.py @@ -1,5 +1,6 @@ from typing import List, Union import base64 +import binascii from enum import IntEnum import msgpack from collections import OrderedDict @@ -260,6 +261,11 @@ def undictify(d): elif txn_type == constants.appcall_txn: args.update(ApplicationCallTxn._undictify(d)) txn = ApplicationCallTxn(**args) + elif txn_type == constants.stateproof_txn: + # a state proof txn does not have these fields + args.pop("note"), args.pop("rekey_to"), args.pop("lease") + args.update(StateProofTxn._undictify(d)) + txn = StateProofTxn(**args) if "grp" in d: txn.group = d["grp"] return txn @@ -2505,13 +2511,54 @@ class LogicSig: """ def __init__(self, program, args=None): - if not program or not logic.check_program(program, args): - raise error.InvalidProgram() + self._sanity_check_program(program) self.logic = program self.args = args self.sig = None self.msig = None + @staticmethod + def _sanity_check_program(program): + """ + Performs heuristic program validation: + check if passed in bytes are Algorand address, or they are B64 encoded, rather than Teal bytes + + Args: + program (bytes): compiled program + """ + + def is_ascii_printable(program_bytes): + return all( + map( + lambda x: x == ord("\n") or (ord(" ") <= x <= ord("~")), + program_bytes, + ) + ) + + if not program: + raise error.InvalidProgram("empty program") + + if is_ascii_printable(program): + try: + encoding.decode_address(program.decode("utf-8")) + raise error.InvalidProgram( + "requesting program bytes, get Algorand address" + ) + except error.WrongChecksumError: + pass + except error.WrongKeyLengthError: + pass + + try: + base64.b64decode(program.decode("utf-8")) + raise error.InvalidProgram("program should not be b64 encoded") + except binascii.Error: + pass + + raise error.InvalidProgram( + "program bytes are all ASCII printable characters, not looking like Teal byte code" + ) + def dictify(self): od = OrderedDict() if self.args: @@ -2548,7 +2595,7 @@ def verify(self, public_key): return False try: - logic.check_program(self.logic, self.args) + self._sanity_check_program(self.logic) except error.InvalidProgram: return False @@ -2906,6 +2953,84 @@ def __eq__(self, other): return False +class StateProofTxn(Transaction): + """ + Represents a state proof transaction + + Arguments: + sender (str): address of the sender + state_proof (dict(), optional) + state_proof_message (dict(), optional) + state_proof_type (str, optional): state proof type + sp (SuggestedParams): suggested params from algod + + + Attributes: + sender (str) + sprf (dict()) + sprfmsg (dict()) + sprf_type (str) + first_valid_round (int) + last_valid_round (int) + genesis_id (str) + genesis_hash (str) + type (str) + """ + + def __init__( + self, + sender, + sp, + state_proof=None, + state_proof_message=None, + state_proof_type=None, + ): + Transaction.__init__( + self, sender, sp, None, None, constants.stateproof_txn, None + ) + + self.sprf_type = state_proof_type + self.sprf = state_proof + self.sprfmsg = state_proof_message + + def dictify(self): + d = dict() + if self.sprf_type: + d["sptype"] = self.sprf_type + if self.sprfmsg: + d["spmsg"] = self.sprfmsg + if self.sprf: + d["sp"] = self.sprf + d.update(super(StateProofTxn, self).dictify()) + od = OrderedDict(sorted(d.items())) + + return od + + @staticmethod + def _undictify(d): + args = {} + if "sptype" in d: + args["state_proof_type"] = d["sptype"] + if "sp" in d: + args["state_proof"] = d["sp"] + if "spmsg" in d: + args["state_proof_message"] = d["spmsg"] + + return args + + def __eq__(self, other): + if not isinstance(other, StateProofTxn): + return False + return ( + super(StateProofTxn, self).__eq__(other) + and self.sprf_type == other.sprf_type + and self.sprf == other.sprf + and self.sprfmsg == other.sprfmsg + ) + + return False + + def write_to_file(txns, path, overwrite=True): """ Write signed or unsigned transactions to a file. diff --git a/algosdk/logic.py b/algosdk/logic.py index 72253d46..05e254c2 100644 --- a/algosdk/logic.py +++ b/algosdk/logic.py @@ -1,6 +1,7 @@ import base64 import json import os +import warnings from . import constants from . import error @@ -14,6 +15,10 @@ def check_program(program, args=None): """ + NOTE: This method is deprecated: + Validation relies on metadata (`langspec.json`) that does not accurately represent opcode behavior across program versions. + The behavior of `check_program` relies on `langspec.json`. Thus, this method is being deprecated. + Performs program checking for max length and cost Args: @@ -26,11 +31,29 @@ def check_program(program, args=None): Raises: InvalidProgram: on error """ + warnings.warn( + "`check_program` relies on metadata (`langspec.json`) that " + "does not accurately represent opcode behavior across program versions. " + "This method is being deprecated.", + DeprecationWarning, + ) ok, _, _ = read_program(program, args) return ok def read_program(program, args=None): + """ + NOTE: This method is deprecated: + Validation relies on metadata (`langspec.json`) that does not accurately represent opcode behavior across program versions. + The behavior of `read_program` relies on `langspec.json`. Thus, this method is being deprecated. + """ + warnings.warn( + "`read_program` relies on metadata (`langspec.json`) that " + "does not accurately represent opcode behavior across program versions. " + "This method is being deprecated.", + DeprecationWarning, + ) + global spec, opcodes intcblock_opcode = 32 bytecblock_opcode = 38 @@ -107,11 +130,25 @@ def read_program(program, args=None): def check_int_const_block(program, pc): + """ + NOTE: This method is deprecated + """ + warnings.warn( + "This method is being deprecated.", + DeprecationWarning, + ) size, _ = read_int_const_block(program, pc) return size def read_int_const_block(program, pc): + """ + NOTE: This method is deprecated + """ + warnings.warn( + "This method is being deprecated.", + DeprecationWarning, + ) size = 1 ints = [] num_ints, bytes_used = parse_uvarint(program[pc + size :]) @@ -134,11 +171,25 @@ def read_int_const_block(program, pc): def check_byte_const_block(program, pc): + """ + NOTE: This method is deprecated + """ + warnings.warn( + "This method is being deprecated.", + DeprecationWarning, + ) size, _ = read_byte_const_block(program, pc) return size def read_byte_const_block(program, pc): + """ + NOTE: This method is deprecated + """ + warnings.warn( + "This method is being deprecated.", + DeprecationWarning, + ) size = 1 bytearrays = [] num_ints, bytes_used = parse_uvarint(program[pc + size :]) @@ -164,11 +215,25 @@ def read_byte_const_block(program, pc): def check_push_int_block(program, pc): + """ + NOTE: This method is deprecated + """ + warnings.warn( + "This method is being deprecated.", + DeprecationWarning, + ) size, _ = read_push_int_block(program, pc) return size def read_push_int_block(program, pc): + """ + NOTE: This method is deprecated + """ + warnings.warn( + "This method is being deprecated.", + DeprecationWarning, + ) size = 1 single_int, bytes_used = parse_uvarint(program[pc + size :]) if bytes_used <= 0: @@ -180,11 +245,25 @@ def read_push_int_block(program, pc): def check_push_byte_block(program, pc): + """ + NOTE: This method is deprecated + """ + warnings.warn( + "This method is being deprecated.", + DeprecationWarning, + ) size, _ = read_push_byte_block(program, pc) return size def read_push_byte_block(program, pc): + """ + NOTE: This method is deprecated + """ + warnings.warn( + "This method is being deprecated.", + DeprecationWarning, + ) size = 1 item_len, bytes_used = parse_uvarint(program[pc + size :]) if bytes_used <= 0: @@ -200,6 +279,13 @@ def read_push_byte_block(program, pc): def parse_uvarint(buf): + """ + NOTE: This method is deprecated + """ + warnings.warn( + "This method is being deprecated.", + DeprecationWarning, + ) x = 0 s = 0 for i, b in enumerate(buf): diff --git a/algosdk/testing/dryrun.py b/algosdk/testing/dryrun.py index 0c54a1a6..bc561e16 100644 --- a/algosdk/testing/dryrun.py +++ b/algosdk/testing/dryrun.py @@ -511,7 +511,7 @@ def build_dryrun_request( ): """ Helper function for creation DryrunRequest object from a program. - By default it uses logic sig mode + By default, it uses logic sig mode and if app_idx / on_complete are set then application call is made Args: diff --git a/algosdk/transaction.py b/algosdk/transaction.py index 8c325b91..f6389a6d 100644 --- a/algosdk/transaction.py +++ b/algosdk/transaction.py @@ -1,5 +1,6 @@ import base64 import msgpack +import binascii from collections import OrderedDict from . import account from . import constants @@ -1318,13 +1319,54 @@ class LogicSig: """ def __init__(self, program, args=None): - if not program or not logic.check_program(program, args): - raise error.InvalidProgram() + self._sanity_check_program(program) self.logic = program self.args = args self.sig = None self.msig = None + @staticmethod + def _sanity_check_program(program): + """ + Performs heuristic program validation: + check if passed in bytes are Algorand address, or they are B64 encoded, rather than Teal bytes + + Args: + program (bytes): compiled program + """ + + def is_ascii_printable(program_bytes): + return all( + map( + lambda x: x == ord("\n") or (ord(" ") <= x <= ord("~")), + program_bytes, + ) + ) + + if not program: + raise error.InvalidProgram("empty program") + + if is_ascii_printable(program): + try: + encoding.decode_address(program.decode("utf-8")) + raise error.InvalidProgram( + "requesting program bytes, get Algorand address" + ) + except error.WrongChecksumError: + pass + except error.WrongKeyLengthError: + pass + + try: + base64.b64decode(program.decode("utf-8")) + raise error.InvalidProgram("program should not be b64 encoded") + except binascii.Error: + pass + + raise error.InvalidProgram( + "program bytes are all ASCII printable characters, not looking like Teal byte code" + ) + def dictify(self): od = OrderedDict() if self.args: @@ -1361,7 +1403,7 @@ def verify(self, public_key): return False try: - logic.check_program(self.logic, self.args) + self._sanity_check_program(self.logic) except error.InvalidProgram: return False diff --git a/algosdk/v2client/algod.py b/algosdk/v2client/algod.py index 7fe4c192..67a21027 100644 --- a/algosdk/v2client/algod.py +++ b/algosdk/v2client/algod.py @@ -397,15 +397,47 @@ def genesis(self, **kwargs): req = "/genesis" return self.algod_request("GET", req, **kwargs) - def proof(self, round_num, txid, **kwargs): + def transaction_proof( + self, round_num, txid, hashtype="", response_format="json", **kwargs + ): """ - Get the proof for a given transaction in a round. + Get a proof for a transaction in a block. Args: round_num (int): The round in which the transaction appears. txid (str): The transaction ID for which to generate a proof. + hashtype (str): The type of hash function used to create the proof, must be either sha512_256 or sha256. """ + params = {"format": response_format} + if hashtype != "": + params["hashtype"] = hashtype req = "/blocks/{}/transactions/{}/proof".format(round_num, txid) + return self.algod_request( + "GET", + req, + params=params, + response_format=response_format, + **kwargs + ) + + def lightblockheader_proof(self, round_num, **kwargs): + """ + Gets a proof for a given light block header inside a state proof commitment. + + Args: + round_num (int): The round to which the light block header belongs. + """ + req = "/blocks/{}/lightheader/proof".format(round_num) + return self.algod_request("GET", req, **kwargs) + + def stateproofs(self, round_num, **kwargs): + """ + Get a state proof that covers a given round + + Args: + round_num (int): The round for which a state proof is desired. + """ + req = "/stateproofs/{}".format(round_num) return self.algod_request("GET", req, **kwargs) diff --git a/run_integration.sh b/run_integration.sh deleted file mode 100755 index 8e680e34..00000000 --- a/run_integration.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -set -e - -rootdir=`dirname $0` -pushd $rootdir - -# Reset test harness -rm -rf test-harness -git clone --single-branch --branch master https://github.com/algorand/algorand-sdk-testing.git test-harness - -## Copy feature files into the project resources -mkdir -p tests/features -cp -r test-harness/features/* tests/features - -# Build SDK testing environment -docker build -t py-sdk-testing --build-arg PYTHON_VERSION="${PYTHON_VERSION}" -f Dockerfile "$(pwd)" - -# Start test harness environment -./test-harness/scripts/up.sh - -while [ $(curl -sL -w "%{http_code}\\n" "http://localhost:59999/v2/accounts" -o /dev/null --connect-timeout 3 --max-time 5) -ne "200" ] -do - sleep 1 -done - -# Launch SDK testing -docker run -it \ - --network host \ - py-sdk-testing:latest diff --git a/setup.py b/setup.py index 3144d7a6..8c5fdc4b 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ description="Algorand SDK in Python", author="Algorand", author_email="pypiservice@algorand.com", - version="v1.16.1", + version="v1.17.0", long_description=long_description, long_description_content_type="text/markdown", license="MIT", diff --git a/test-harness.sh b/test-harness.sh new file mode 100755 index 00000000..9ddda66b --- /dev/null +++ b/test-harness.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +set -euo pipefail + +START=$(date "+%s") + +THIS=$(basename "$0") +ENV_FILE=".test-env" +TEST_DIR="tests" + +set -a +source "$ENV_FILE" +set +a + +rootdir=$(dirname "$0") +pushd "$rootdir" + +echo "$THIS: VERBOSE_HARNESS=$VERBOSE_HARNESS" + +## Reset test harness +if [ -d "$SDK_TESTING_HARNESS" ]; then + pushd "$SDK_TESTING_HARNESS" + ./scripts/down.sh + popd + rm -rf "$SDK_TESTING_HARNESS" +else + echo "$THIS: directory $SDK_TESTING_HARNESS does not exist - NOOP" +fi + +git clone --depth 1 --single-branch --branch "$SDK_TESTING_BRANCH" "$SDK_TESTING_URL" "$SDK_TESTING_HARNESS" + + +echo "$THIS: OVERWRITE_TESTING_ENVIRONMENT=$OVERWRITE_TESTING_ENVIRONMENT" +if [[ $OVERWRITE_TESTING_ENVIRONMENT == 1 ]]; then + echo "$THIS: OVERWRITE replaced $SDK_TESTING_HARNESS/.env with $ENV_FILE:" + cp "$ENV_FILE" "$SDK_TESTING_HARNESS"/.env +fi + + +echo "$THIS: REMOVE_LOCAL_FEATURES=$REMOVE_LOCAL_FEATURES" +## Copy feature files into the project resources +if [[ $REMOVE_LOCAL_FEATURES == 1 ]]; then + echo "$THIS: OVERWRITE wipes clean $TEST_DIR/features" + if [[ $VERBOSE_HARNESS == 1 ]]; then + ( tree $TEST_DIR/features && echo "$THIS: see the previous for files deleted" ) || true + fi + rm -rf $TEST_DIR/features +fi +mkdir -p $TEST_DIR/features +cp -r "$SDK_TESTING_HARNESS"/features/* $TEST_DIR/features +if [[ $VERBOSE_HARNESS == 1 ]]; then + ( tree $TEST_DIR/features && echo "$THIS: see the previous for files copied over" ) || true +fi +echo "$THIS: seconds it took to get to end of cloning and copying: $(($(date "+%s") - START))s" + +## Start test harness environment +pushd "$SDK_TESTING_HARNESS" + +[[ "$VERBOSE_HARNESS" = 1 ]] && V_FLAG="-v" || V_FLAG="" +echo "$THIS: standing up harnness with command [./up.sh $V_FLAG]" +./scripts/up.sh "$V_FLAG" + +popd +echo "$THIS: seconds it took to finish testing sdk's up.sh: $(($(date "+%s") - START))s" +echo "" +echo "--------------------------------------------------------------------------------" +echo "|" +echo "| To run sandbox commands, cd into $SDK_TESTING_HARNESS/.sandbox " +echo "|" +echo "--------------------------------------------------------------------------------" diff --git a/tests/integration.tags b/tests/integration.tags new file mode 100644 index 00000000..6b2da89c --- /dev/null +++ b/tests/integration.tags @@ -0,0 +1,15 @@ +@abi +@algod +@applications +@applications.verified +@assets +@auction +@c2c +@compile +@compile.sourcemap +@dryrun +@dryrun.testing +@kmd +@rekey_v1 +@send +@send.keyregtxn \ No newline at end of file diff --git a/tests/steps/account_v2_steps.py b/tests/steps/account_v2_steps.py index 5778f4aa..28764cbd 100644 --- a/tests/steps/account_v2_steps.py +++ b/tests/steps/account_v2_steps.py @@ -1,6 +1,6 @@ from typing import Union -from algosdk import account, constants, encoding, logic +from algosdk import account, encoding, logic from algosdk.future import transaction from behave import given, then, when import tests.steps.other_v2_steps # Imports MaybeString @@ -33,57 +33,6 @@ def acc_info2(context, account): context.response = context.acl.account_info(account) -@then( - 'The account has {num} assets, the first is asset {index} has a frozen status of "{frozen}" and amount {units}.' -) -def lookup_account_check(context, num, index, frozen, units): - assert len(context.response["account"]["assets"]) == int(num) - assert context.response["account"]["assets"][0]["asset-id"] == int(index) - assert context.response["account"]["assets"][0]["is-frozen"] == ( - frozen == "true" - ) - assert context.response["account"]["assets"][0]["amount"] == int(units) - - -@then( - 'The account created {num} assets, the first is asset {index} is named "{name}" with a total amount of {total} "{unit}"' -) -def lookup_account_check_created(context, num, index, name, total, unit): - assert len(context.response["account"]["created-assets"]) == int(num) - assert context.response["account"]["created-assets"][0]["index"] == int( - index - ) - assert ( - context.response["account"]["created-assets"][0]["params"]["name"] - == name - ) - assert ( - context.response["account"]["created-assets"][0]["params"]["unit-name"] - == unit - ) - assert context.response["account"]["created-assets"][0]["params"][ - "total" - ] == int(total) - - -@then( - "The account has {μalgos} μalgos and {num} assets, {assetid} has {assetamount}" -) -def lookup_account_check_holdings(context, μalgos, num, assetid, assetamount): - assert context.response["account"]["amount"] == int(μalgos) - assert len(context.response["account"].get("assets", [])) == int(num) - if int(num) > 0: - assets = context.response["account"]["assets"] - for a in assets: - if a["asset-id"] == int(assetid): - assert a["amount"] == int(assetamount) - - -@when('I use {indexer} to lookup account "{account}" at round {round}') -def icl_lookup_account_at_round(context, indexer, account, round): - context.response = context.icls[indexer].account_info(account, int(round)) - - @when( 'we make a Lookup Account by ID call against account "{account}" with round {block}' ) @@ -94,7 +43,7 @@ def lookup_account(context, account, block): @when( 'we make a Lookup Account by ID call against account "{account}" with exclude "{exclude:MaybeString}"' ) -def lookup_account(context, account, exclude): +def lookup_account2(context, account, exclude): context.response = context.icl.account_info(account, exclude=exclude) @@ -262,151 +211,6 @@ def search_accounts3( context.response = context.icl.accounts(exclude=exclude) -@when( - 'I use {indexer} to search for an account with {assetid}, {limit}, {currencygt}, {currencylt}, "{auth_addr:MaybeString}", {application_id}, "{include_all:MaybeBool}" and token "{token:MaybeString}"' -) -def icl_search_accounts_with_auth_addr_and_app_id_and_include_all( - context, - indexer, - assetid, - limit, - currencygt, - currencylt, - auth_addr, - application_id, - include_all, - token, -): - context.response = context.icls[indexer].accounts( - asset_id=int(assetid), - limit=int(limit), - next_page=token, - min_balance=int(currencygt), - max_balance=int(currencylt), - auth_addr=auth_addr, - application_id=int(application_id), - include_all=include_all, - ) - - -@when( - 'I use {indexer} to search for an account with {assetid}, {limit}, {currencygt}, {currencylt}, "{auth_addr:MaybeString}", {application_id} and token "{token:MaybeString}"' -) -def icl_search_accounts_with_auth_addr_and_app_id( - context, - indexer, - assetid, - limit, - currencygt, - currencylt, - auth_addr, - application_id, - token, -): - context.response = context.icls[indexer].accounts( - asset_id=int(assetid), - limit=int(limit), - next_page=token, - min_balance=int(currencygt), - max_balance=int(currencylt), - auth_addr=auth_addr, - application_id=int(application_id), - ) - - -@when( - 'I use {indexer} to search for an account with {assetid}, {limit}, {currencygt}, {currencylt} and token "{token:MaybeString}"' -) -def icl_search_accounts_legacy( - context, indexer, assetid, limit, currencygt, currencylt, token -): - context.response = context.icls[indexer].accounts( - asset_id=int(assetid), - limit=int(limit), - next_page=token, - min_balance=int(currencygt), - max_balance=int(currencylt), - ) - - -@then( - "I get the next page using {indexer} to search for an account with {assetid}, {limit}, {currencygt} and {currencylt}" -) -def search_accounts_nex( - context, indexer, assetid, limit, currencygt, currencylt -): - context.response = context.icls[indexer].accounts( - asset_id=int(assetid), - limit=int(limit), - min_balance=int(currencygt), - max_balance=int(currencylt), - next_page=context.response["next-token"], - ) - - -@then( - 'There are {num}, the first has {pendingrewards}, {rewardsbase}, {rewards}, {withoutrewards}, "{address}", {amount}, "{status}", "{sigtype:MaybeString}"' -) -def check_search_accounts( - context, - num, - pendingrewards, - rewardsbase, - rewards, - withoutrewards, - address, - amount, - status, - sigtype, -): - assert len(context.response["accounts"]) == int(num) - assert context.response["accounts"][0]["pending-rewards"] == int( - pendingrewards - ) - assert context.response["accounts"][0].get("rewards-base", 0) == int( - rewardsbase - ) - assert context.response["accounts"][0]["rewards"] == int(rewards) - assert context.response["accounts"][0][ - "amount-without-pending-rewards" - ] == int(withoutrewards) - assert context.response["accounts"][0]["address"] == address - assert context.response["accounts"][0]["amount"] == int(amount) - assert context.response["accounts"][0]["status"] == status - assert context.response["accounts"][0].get("sig-type", "") == sigtype - - -@then( - 'The first account is online and has "{address}", {keydilution}, {firstvalid}, {lastvalid}, "{votekey}", "{selectionkey}"' -) -def check_search_accounts_online( - context, address, keydilution, firstvalid, lastvalid, votekey, selectionkey -): - assert context.response["accounts"][0]["status"] == "Online" - assert context.response["accounts"][0]["address"] == address - assert context.response["accounts"][0]["participation"][ - "vote-key-dilution" - ] == int(keydilution) - assert context.response["accounts"][0]["participation"][ - "vote-first-valid" - ] == int(firstvalid) - assert context.response["accounts"][0]["participation"][ - "vote-last-valid" - ] == int(lastvalid) - assert ( - context.response["accounts"][0]["participation"][ - "vote-participation-key" - ] - == votekey - ) - assert ( - context.response["accounts"][0]["participation"][ - "selection-participation-key" - ] - == selectionkey - ) - - @when("we make any SearchAccounts call") def search_accounts_any(context): context.response = context.icl.accounts(asset_id=2) diff --git a/tests/steps/application_v2_steps.py b/tests/steps/application_v2_steps.py index e480ff1a..e733159f 100644 --- a/tests/steps/application_v2_steps.py +++ b/tests/steps/application_v2_steps.py @@ -9,7 +9,6 @@ from algosdk.error import ( ABITypeError, AtomicTransactionComposerError, - IndexerHTTPError, ) from algosdk.future import transaction from behave import given, step, then, when @@ -143,69 +142,11 @@ def search_application2(context, creator): context.response = context.icl.search_applications(creator=creator) -@when( - 'I use {indexer} to search for applications with {limit}, {application_id}, "{include_all:MaybeBool}" and token "{token:MaybeString}"' -) -def search_applications_include_all( - context, indexer, limit, application_id, include_all, token -): - context.response = context.icls[indexer].search_applications( - application_id=int(application_id), - limit=int(limit), - include_all=include_all, - next_page=token, - ) - - -@when( - 'I use {indexer} to search for applications with {limit}, {application_id}, and token "{token:MaybeString}"' -) -def search_applications(context, indexer, limit, application_id, token): - context.response = context.icls[indexer].search_applications( - application_id=int(application_id), limit=int(limit), next_page=token - ) - - -@when( - 'I use {indexer} to lookup application with {application_id} and "{include_all:MaybeBool}"' -) -def lookup_application_include_all2( - context, indexer, application_id, include_all -): - try: - context.response = context.icls[indexer].applications( - application_id=int(application_id), include_all=include_all - ) - except IndexerHTTPError as e: - context.response = json.loads(str(e)) - - @when("we make a LookupApplications call with applicationID {app_id}") def lookup_application(context, app_id): context.response = context.icl.applications(int(app_id)) -@when("I use {indexer} to lookup application with {application_id}") -def lookup_application2(context, indexer, application_id): - context.response = context.icls[indexer].applications( - application_id=int(application_id) - ) - - -@when("we make a SearchForApplications call with {application_id} and {round}") -def search_applications2(context, application_id, round): - context.response = context.icl.search_applications( - application_id=int(application_id), round=int(round) - ) - - -@when("we make a LookupApplications call with {application_id} and {round}") -def lookup_applications(context, application_id, round): - context.response = context.icl.applications( - application_id=int(application_id), round=int(round) - ) - - @when( 'I build an application transaction with operation "{operation:MaybeString}", application-id {application_id}, sender "{sender:MaybeString}", approval-program "{approval_program:MaybeString}", clear-program "{clear_program:MaybeString}", global-bytes {global_bytes}, global-ints {global_ints}, local-bytes {local_bytes}, local-ints {local_ints}, app-args "{app_args:MaybeString}", foreign-apps "{foreign_apps:MaybeString}", foreign-assets "{foreign_assets:MaybeString}", app-accounts "{app_accounts:MaybeString}", fee {fee}, first-valid {first_valid}, last-valid {last_valid}, genesis-hash "{genesis_hash:MaybeString}", extra-pages {extra_pages}' ) @@ -413,14 +354,16 @@ def wait_for_algod_transaction_processing_to_complete(): time.sleep(0.5) +# TODO: this needs to be modified to use v2 only @step("I wait for the transaction to be confirmed.") def wait_for_app_txn_confirm(context): wait_for_algod_transaction_processing_to_complete() if hasattr(context, "acl"): + # TODO: get rid of this branch of logic when v1 fully deprecated assert "type" in context.acl.transaction_info( context.transient_pk, context.app_txid ) - assert "type" in context.acl.transaction_by_id(context.app_txid) + # assert "type" in context.acl.transaction_by_id(context.app_txid) else: transaction.wait_for_confirmation(context.app_acl, context.app_txid, 1) diff --git a/tests/steps/other_v2_steps.py b/tests/steps/other_v2_steps.py index a5367755..58f6295c 100644 --- a/tests/steps/other_v2_steps.py +++ b/tests/steps/other_v2_steps.py @@ -3,7 +3,6 @@ import os import unittest import urllib -from datetime import datetime from pathlib import Path from urllib.request import Request, urlopen @@ -333,31 +332,6 @@ def parse_block(context, pool): assert context.response["block"]["rwd"] == pool -@when( - "I get the next page using {indexer} to lookup asset balances for {assetid} with {currencygt}, {currencylt}, {limit}" -) -def next_asset_balance( - context, indexer, assetid, currencygt, currencylt, limit -): - context.response = context.icls[indexer].asset_balances( - int(assetid), - min_balance=int(currencygt), - max_balance=int(currencylt), - limit=int(limit), - next_page=context.response["next-token"], - ) - - -@then( - 'There are {numaccounts} with the asset, the first is "{account}" has "{isfrozen}" and {amount}' -) -def check_asset_balance(context, numaccounts, account, isfrozen, amount): - assert len(context.response["balances"]) == int(numaccounts) - assert context.response["balances"][0]["address"] == account - assert context.response["balances"][0]["amount"] == int(amount) - assert context.response["balances"][0]["is-frozen"] == (isfrozen == "true") - - @when( 'we make a Lookup Asset Balances call against asset index {index} with limit {limit} afterAddress "{afterAddress:MaybeString}" currencyGreaterThan {currencyGreaterThan} currencyLessThan {currencyLessThan}' ) @@ -383,13 +357,6 @@ def asset_balance_any(context): context.response = context.icl.asset_balances(123, 10) -@when("I use {indexer} to search for all {assetid} asset transactions") -def icl_asset_txns(context, indexer, assetid): - context.response = context.icls[indexer].search_asset_transactions( - int(assetid) - ) - - @when( 'we make a Lookup Asset Transactions call against asset index {index} with NotePrefix "{notePrefixB64:MaybeString}" TxType "{txType:MaybeString}" SigType "{sigType:MaybeString}" txid "{txid:MaybeString}" round {block} minRound {minRound} maxRound {maxRound} limit {limit} beforeTime "{beforeTime:MaybeString}" afterTime "{afterTime:MaybeString}" currencyGreaterThan {currencyGreaterThan} currencyLessThan {currencyLessThan} address "{address:MaybeString}" addressRole "{addressRole:MaybeString}" ExcluseCloseTo "{excludeCloseTo:MaybeString}" RekeyTo "{rekeyTo:MaybeString}"' ) @@ -532,13 +499,6 @@ def parse_asset_tns(context, roundNum, length, idx, sender): assert context.response["transactions"][int(idx)]["sender"] == sender -@when('I use {indexer} to search for all "{accountid}" transactions') -def icl_txns_by_addr(context, indexer, accountid): - context.response = context.icls[indexer].search_transactions_by_address( - accountid - ) - - @when( 'we make a Lookup Account Transactions call against account "{account:MaybeString}" with NotePrefix "{notePrefixB64:MaybeString}" TxType "{txType:MaybeString}" SigType "{sigType:MaybeString}" txid "{txid:MaybeString}" round {block} minRound {minRound} maxRound {maxRound} limit {limit} beforeTime "{beforeTime:MaybeString}" afterTime "{afterTime:MaybeString}" currencyGreaterThan {currencyGreaterThan} currencyLessThan {currencyLessThan} assetIndex {index} rekeyTo "{rekeyTo:MaybeString}"' ) @@ -663,31 +623,6 @@ def parse_txns_by_addr(context, roundNum, length, idx, sender): assert context.response["transactions"][int(idx)]["sender"] == sender -@when("I use {indexer} to check the services health") -def icl_health(context, indexer): - context.response = context.icls[indexer].health() - - -@then("I receive status code {code}") -def icl_health_check(context, code): - # An exception is thrown when the code is not 200 - assert int(code) == 200 - - -@when("I use {indexer} to lookup block {number}") -def icl_lookup_block(context, indexer, number): - context.response = context.icls[indexer].block_info(int(number)) - - -@then( - 'The block was confirmed at {timestamp}, contains {num} transactions, has the previous block hash "{prevHash}"' -) -def icl_block_check(context, timestamp, num, prevHash): - assert context.response["previous-block-hash"] == prevHash - assert len(context.response["transactions"]) == int(num) - assert context.response["timestamp"] == int(timestamp) - - @when("we make a Lookup Block call against round {block}") def lookup_block(context, block): context.response = context.icl.block_info(int(block)) @@ -705,21 +640,6 @@ def parse_lookup_block(context, prevHash): assert context.response["previous-block-hash"] == prevHash -@when( - 'I use {indexer} to lookup asset balances for {assetid} with {currencygt}, {currencylt}, {limit} and token "{token}"' -) -def icl_asset_balance( - context, indexer, assetid, currencygt, currencylt, limit, token -): - context.response = context.icls[indexer].asset_balances( - int(assetid), - min_balance=int(currencygt), - max_balance=int(currencylt), - limit=int(limit), - next_page=token, - ) - - def parse_args(assetid): t = assetid.split(" ") l = { @@ -732,31 +652,6 @@ def parse_args(assetid): return l -@when("I use {indexer} to lookup asset {assetid}") -def icl_lookup_asset(context, indexer, assetid): - try: - context.response = context.icls[indexer].asset_info(int(assetid)) - except: - icl_asset_balance(context, indexer, **parse_args(assetid)) - - -@then( - 'The asset found has: "{name}", "{units}", "{creator}", {decimals}, "{defaultfrozen}", {total}, "{clawback}"' -) -def check_lookup_asset( - context, name, units, creator, decimals, defaultfrozen, total, clawback -): - assert context.response["asset"]["params"]["name"] == name - assert context.response["asset"]["params"]["unit-name"] == units - assert context.response["asset"]["params"]["creator"] == creator - assert context.response["asset"]["params"]["decimals"] == int(decimals) - assert context.response["asset"]["params"]["default-frozen"] == ( - defaultfrozen == "true" - ) - assert context.response["asset"]["params"]["total"] == int(total) - assert context.response["asset"]["params"]["clawback"] == clawback - - @when("we make a Lookup Asset by ID call against asset index {index}") def lookup_asset(context, index): context.response = context.icl.asset_info(int(index)) @@ -772,200 +667,6 @@ def parse_asset(context, index): assert context.response["asset"]["index"] == int(index) -@when( - "I get the next page using {indexer} to search for transactions with {limit} and {maxround}" -) -def search_txns_next(context, indexer, limit, maxround): - context.response = context.icls[indexer].search_transactions( - limit=int(limit), - max_round=int(maxround), - next_page=context.response["next-token"], - ) - - -@when( - 'I use {indexer} to search for transactions with {limit}, "{noteprefix:MaybeString}", "{txtype:MaybeString}", "{sigtype:MaybeString}", "{txid:MaybeString}", {block}, {minround}, {maxround}, {assetid}, "{beforetime:MaybeString}", "{aftertime:MaybeString}", {currencygt}, {currencylt}, "{address:MaybeString}", "{addressrole:MaybeString}", "{excludecloseto:MaybeString}" and token "{token:MaybeString}"' -) -def icl_search_txns( - context, - indexer, - limit, - noteprefix, - txtype, - sigtype, - txid, - block, - minround, - maxround, - assetid, - beforetime, - aftertime, - currencygt, - currencylt, - address, - addressrole, - excludecloseto, - token, -): - context.response = context.icls[indexer].search_transactions( - asset_id=int(assetid), - limit=int(limit), - next_page=token, - note_prefix=base64.b64decode(noteprefix), - txn_type=txtype, - sig_type=sigtype, - txid=txid, - block=int(block), - min_round=int(minround), - max_round=int(maxround), - start_time=aftertime, - end_time=beforetime, - min_amount=int(currencygt), - max_amount=int(currencylt), - address=address, - address_role=addressrole, - exclude_close_to=excludecloseto == "true", - ) - - -@when( - 'I use {indexer} to search for transactions with {limit}, "{noteprefix:MaybeString}", "{txtype:MaybeString}", "{sigtype:MaybeString}", "{txid:MaybeString}", {block}, {minround}, {maxround}, {assetid}, "{beforetime:MaybeString}", "{aftertime:MaybeString}", {currencygt}, {currencylt}, "{address:MaybeString}", "{addressrole:MaybeString}", "{excludecloseto:MaybeString}", {application_id} and token "{token:MaybeString}"' -) -def icl_search_txns_with_app( - context, - indexer, - limit, - noteprefix, - txtype, - sigtype, - txid, - block, - minround, - maxround, - assetid, - beforetime, - aftertime, - currencygt, - currencylt, - address, - addressrole, - excludecloseto, - application_id, - token, -): - context.response = context.icls[indexer].search_transactions( - asset_id=int(assetid), - limit=int(limit), - next_page=token, - note_prefix=base64.b64decode(noteprefix), - txn_type=txtype, - sig_type=sigtype, - txid=txid, - block=int(block), - min_round=int(minround), - max_round=int(maxround), - start_time=aftertime, - end_time=beforetime, - min_amount=int(currencygt), - max_amount=int(currencylt), - address=address, - address_role=addressrole, - application_id=int(application_id), - exclude_close_to=excludecloseto == "true", - ) - - -@then( - 'there are {num} transactions in the response, the first is "{txid:MaybeString}".' -) -def check_transactions(context, num, txid): - assert len(context.response["transactions"]) == int(num) - if int(num) > 0: - assert context.response["transactions"][0]["id"] == txid - - -@then('Every transaction has tx-type "{txtype}"') -def check_transaction_types(context, txtype): - for txn in context.response["transactions"]: - assert txn["tx-type"] == txtype - - -@then('Every transaction has sig-type "{sigtype}"') -def check_sig_types(context, sigtype): - for txn in context.response["transactions"]: - if sigtype == "lsig": - assert list(txn["signature"].keys())[0] == "logicsig" - if sigtype == "msig": - assert list(txn["signature"].keys())[0] == "multisig" - if sigtype == "sig": - assert list(txn["signature"].keys())[0] == sigtype - - -@then("Every transaction has round >= {minround}") -def check_minround(context, minround): - for txn in context.response["transactions"]: - assert txn["confirmed-round"] >= int(minround) - - -@then("Every transaction has round <= {maxround}") -def check_maxround(context, maxround): - for txn in context.response["transactions"]: - assert txn["confirmed-round"] <= int(maxround) - - -@then("Every transaction has round {block}") -def check_round(context, block): - for txn in context.response["transactions"]: - assert txn["confirmed-round"] == int(block) - - -@then("Every transaction works with asset-id {assetid}") -def check_assetid(context, assetid): - for txn in context.response["transactions"]: - if "asset-config-transaction" in txn: - subtxn = txn["asset-config-transaction"] - else: - subtxn = txn["asset-transfer-transaction"] - assert subtxn["asset-id"] == int(assetid) or txn[ - "created-asset-index" - ] == int(assetid) - - -@then('Every transaction is older than "{before}"') -def check_before(context, before): - for txn in context.response["transactions"]: - t = datetime.fromisoformat(before.replace("Z", "+00:00")) - assert txn["round-time"] <= datetime.timestamp(t) - - -@then('Every transaction is newer than "{after}"') -def check_after(context, after): - t = True - for txn in context.response["transactions"]: - t = datetime.fromisoformat(after.replace("Z", "+00:00")) - if not txn["round-time"] >= datetime.timestamp(t): - t = False - assert t - - -@then("Every transaction moves between {currencygt} and {currencylt} currency") -def check_currency(context, currencygt, currencylt): - for txn in context.response["transactions"]: - amt = 0 - if "asset-transfer-transaction" in txn: - amt = txn["asset-transfer-transaction"]["amount"] - else: - amt = txn["payment-transaction"]["amount"] - if int(currencygt) == 0: - if int(currencylt) > 0: - assert amt <= int(currencylt) - else: - if int(currencylt) > 0: - assert int(currencygt) <= amt <= int(currencylt) - else: - assert int(currencygt) <= amt - - @when( 'we make a Search For Transactions call with account "{account:MaybeString}" NotePrefix "{notePrefixB64:MaybeString}" TxType "{txType:MaybeString}" SigType "{sigType:MaybeString}" txid "{txid:MaybeString}" round {block} minRound {minRound} maxRound {maxRound} limit {limit} beforeTime "{beforeTime:MaybeString}" afterTime "{afterTime:MaybeString}" currencyGreaterThan {currencyGreaterThan} currencyLessThan {currencyLessThan} assetIndex {index} addressRole "{addressRole:MaybeString}" ExcluseCloseTo "{excludeCloseTo:MaybeString}" rekeyTo "{rekeyTo:MaybeString}"' ) @@ -1120,66 +821,6 @@ def parsed_search_for_txns(context, roundNum, length, index, rekeyTo): ) -@when( - 'I use {indexer} to search for assets with {limit}, {assetidin}, "{creator:MaybeString}", "{name:MaybeString}", "{unit:MaybeString}", and token "{token:MaybeString}"' -) -def icl_search_assets( - context, indexer, limit, assetidin, creator, name, unit, token -): - context.response = context.icls[indexer].search_assets( - limit=int(limit), - next_page=token, - creator=creator, - name=name, - unit=unit, - asset_id=int(assetidin), - ) - - -@then("there are {num} assets in the response, the first is {assetidout}.") -def check_assets(context, num, assetidout): - assert len(context.response["assets"]) == int(num) - if int(num) > 0: - assert context.response["assets"][0]["index"] == int(assetidout) - - -@then('the parsed response should equal "{jsonfile}".') -def parsed_equals(context, jsonfile): - loaded_response = None - dir_path = os.path.dirname(os.path.realpath(__file__)) - dir_path = os.path.dirname(os.path.dirname(dir_path)) - with open(dir_path + "/tests/features/resources/" + jsonfile, "rb") as f: - loaded_response = bytearray(f.read()) - # sort context.response - def recursively_sort_on_key(dictionary): - returned_dict = dict() - for k, v in sorted(dictionary.items()): - if isinstance(v, dict): - returned_dict[k] = recursively_sort_on_key(v) - elif isinstance(v, list) and all( - isinstance(item, dict) for item in v - ): - if all("key" in item.keys() for item in v): - from operator import itemgetter - - returned_dict[k] = sorted(v, key=itemgetter("key")) - else: - sorted_list = list() - for item in v: - sorted_list.append(recursively_sort_on_key(item)) - returned_dict[k] = sorted_list - else: - returned_dict[k] = v - return returned_dict - - context.response = recursively_sort_on_key(context.response) - loaded_response = recursively_sort_on_key(json.loads(loaded_response)) - if context.response != loaded_response: - print("EXPECTED: " + str(loaded_response)) - print("ACTUAL: " + str(context.response)) - assert context.response == loaded_response - - @when( 'we make a SearchForAssets call with limit {limit} creator "{creator:MaybeString}" name "{name:MaybeString}" unit "{unit:MaybeString}" index {index}' ) @@ -1248,28 +889,12 @@ def expect_path(context, path): assert exp_query == actual_query -@then('we expect the path used to be "{path}"') -def we_expect_path(context, path): - expect_path(context, path) - - @then('expect error string to contain "{err:MaybeString}"') def expect_error(context, err): # TODO: this should actually do the claimed action pass -@given( - 'indexer client {index} at "{address}" port {port} with token "{token}"' -) -def indexer_client(context, index, address, port, token): - if not hasattr(context, "icls"): - context.icls = dict() - context.icls[index] = indexer.IndexerClient( - token, "http://" + address + ":" + str(port) - ) - - @given( 'suggested transaction parameters fee {fee}, flat-fee "{flat_fee:MaybeBool}", first-valid {first_valid}, last-valid {last_valid}, genesis-hash "{genesis_hash}", genesis-id "{genesis_id}"' ) @@ -1744,4 +1369,26 @@ def check_compile_mapping(context, teal): @then('the resulting source map is the same as the json "{sourcemap}"') def check_mapping_equal(context, sourcemap): expected = load_resource(sourcemap).decode("utf-8").strip() - assert context.raw_source_map == expected + nl = "\n" + assert ( + context.raw_source_map == expected + ), f"context.raw_source_map={nl}{context.raw_source_map}{nl}expected={nl}{expected}" + + +@when("we make a GetLightBlockHeaderProof call for round {round}") +def lightblock(context, round): + context.response = context.acl.lightblockheader_proof(round) + + +@when("we make a GetStateProof call for round {round}") +def state_proofs(context, round): + context.response = context.acl.stateproofs(round) + + +@when( + 'we make a GetTransactionProof call for round {round} txid "{txid}" and hashtype "{hashtype:MaybeString}"' +) +def transaction_proof(context, round, txid, hashtype): + context.response = context.acl.transaction_proof( + round, txid, hashtype, "msgpack" + ) diff --git a/tests/steps/steps.py b/tests/steps/steps.py index 59102280..ab865239 100644 --- a/tests/steps/steps.py +++ b/tests/steps/steps.py @@ -1,7 +1,8 @@ -import base64 import os +import base64 import random import time +import parse from datetime import datetime from algosdk import ( @@ -16,9 +17,27 @@ wallet, ) from algosdk.future import transaction -from behave import given, then, when +from algosdk import encoding +from algosdk import algod +from algosdk import account +from algosdk import mnemonic +from algosdk import wallet +from algosdk import auction +from algosdk import util +from algosdk import logic + +from behave import given, then, when, register_type from nacl.signing import SigningKey + +@parse.with_pattern(r".*") +def parse_string(text): + return text + + +register_type(MaybeString=parse_string) + + token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" algod_port = 60000 kmd_port = 60001 @@ -177,18 +196,6 @@ def mn_for_sk(context, mn): context.pk = account.address_from_private_key(context.sk) -@when("I create the payment transaction") -def create_paytxn(context): - context.txn = transaction.PaymentTxn( - context.pk, - context.params, - context.to, - context.amt, - context.close, - context.note, - ) - - @given('multisig addresses "{addresses}"') def msig_addresses(context, addresses): addresses = addresses.split(" ") @@ -466,6 +473,7 @@ def send_msig_txn(context): context.error = True +# TODO: this needs to be modified/removed when v1 is no longer supported!!! @then("the transaction should go through") def check_txn(context): wait_for_algod_transaction_processing_to_complete() @@ -475,13 +483,7 @@ def check_txn(context): assert "type" in context.acl.transaction_info( context.txn.sender, context.txn.get_txid() ) - assert "type" in context.acl.transaction_by_id(context.txn.get_txid()) - - -@then("I can get the transaction by ID") -def get_txn_by_id(context): - wait_for_algod_transaction_processing_to_complete() - assert "type" in context.acl.transaction_by_id(context.txn.get_txid()) + # assert "type" in context.acl.transaction_by_id(context.txn.get_txid()) @then("the transaction should not go through") @@ -517,48 +519,6 @@ def sign_msig_both_equal(context): ) -@when('I read a transaction "{txn}" from file "{num}"') -def read_txn(context, txn, num): - dir_path = os.path.dirname(os.path.realpath(__file__)) - dir_path = os.path.dirname(os.path.dirname(dir_path)) - context.num = num - context.txn = transaction.retrieve_from_file( - dir_path + "/temp/raw" + num + ".tx" - )[0] - - -@when("I write the transaction to file") -def write_txn(context): - dir_path = os.path.dirname(os.path.realpath(__file__)) - dir_path = os.path.dirname(os.path.dirname(dir_path)) - transaction.write_to_file( - [context.txn], dir_path + "/temp/raw" + context.num + ".tx" - ) - - -@then("the transaction should still be the same") -def check_enc(context): - dir_path = os.path.dirname(os.path.realpath(__file__)) - dir_path = os.path.dirname(os.path.dirname(dir_path)) - new = transaction.retrieve_from_file( - dir_path + "/temp/raw" + context.num + ".tx" - ) - old = transaction.retrieve_from_file( - dir_path + "/temp/old" + context.num + ".tx" - ) - assert encoding.msgpack_encode(new[0]) == encoding.msgpack_encode(old[0]) - - -@then("I do my part") -def check_save_txn(context): - dir_path = os.path.dirname(os.path.realpath(__file__)) - dir_path = os.path.dirname(os.path.dirname(dir_path)) - stx = transaction.retrieve_from_file(dir_path + "/temp/txn.tx")[0] - txid = stx.transaction.get_txid() - wait_for_algod_transaction_processing_to_complete() - assert context.acl.transaction_info(stx.transaction.sender, txid) - - @then("I get the ledger supply") def get_ledger(context): context.acl.ledger_supply() @@ -696,21 +656,6 @@ def txns_by_addr_round(context): assert txns == {} or "transactions" in txns -@then("I get transactions by address only") -def txns_by_addr_only(context): - txns = context.acl.transactions_by_address(context.accounts[0]) - assert txns == {} or "transactions" in txns - - -@then("I get transactions by address and date") -def txns_by_addr_date(context): - date = datetime.today().strftime("%Y-%m-%d") - txns = context.acl.transactions_by_address( - context.accounts[0], from_date=date, to_date=date - ) - assert txns == {} or "transactions" in txns - - @then("I get pending transactions") def txns_pending(context): txns = context.acl.pending_transactions() @@ -728,64 +673,6 @@ def new_acc_info(context): context.wallet.delete_key(context.pk) -@given( - 'key registration transaction parameters {fee} {fv} {lv} "{gh}" "{votekey}" "{selkey}" {votefst} {votelst} {votekd} "{gen}" "{note}"' -) -def keyreg_txn_params( - context, - fee, - fv, - lv, - gh, - votekey, - selkey, - votefst, - votelst, - votekd, - gen, - note, -): - context.fee = int(fee) - context.fv = int(fv) - context.lv = int(lv) - context.gh = gh - context.votekey = votekey - context.selkey = selkey - context.votefst = int(votefst) - context.votelst = int(votelst) - context.votekd = int(votekd) - if gen == "none": - context.gen = None - else: - context.gen = gen - context.params = transaction.SuggestedParams( - context.fee, context.fv, context.lv, context.gh, context.gen - ) - - if note == "none": - context.note = None - else: - context.note = base64.b64decode(note) - if gen == "none": - context.gen = None - else: - context.gen = gen - - -@when("I create the key registration transaction") -def create_keyreg_txn(context): - context.txn = transaction.KeyregOnlineTxn( - context.pk, - context.params, - context.votekey, - context.selkey, - context.votefst, - context.votelst, - context.votekd, - context.note, - ) - - @given("default V2 key registration transaction {type}") def default_v2_keyreg_txn(context, type): context.params = context.acl.suggested_params_as_object() @@ -793,14 +680,6 @@ def default_v2_keyreg_txn(context, type): context.txn = buildTxn(type, context.pk, context.params) -@when("I get recent transactions, limited by {cnt} transactions") -def step_impl(context, cnt): - txns = context.acl.transactions_by_address( - context.accounts[0], limit=int(cnt) - ) - assert txns == {} or "transactions" in txns - - @given("default asset creation transaction with total issuance {total}") def default_asset_creation_txn(context, total): context.total = int(total) @@ -1126,3 +1005,30 @@ def buildTxn(t, sender, params): elif "nonparticipation" in t: txn = transaction.KeyregNonparticipatingTxn(sender, params) return txn + + +@given( + 'a base64 encoded program bytes for heuristic sanity check "{b64encoded:MaybeString}"' +) +def take_b64_encoded_bytes(context, b64encoded): + context.seemingly_program = base64.b64decode(b64encoded) + + +@when("I start heuristic sanity check over the bytes") +def heuristic_check_over_bytes(context): + context.sanity_check_err = "" + + try: + transaction.LogicSig(context.seemingly_program) + except Exception as e: + context.sanity_check_err = str(e) + + +@then( + 'if the heuristic sanity check throws an error, the error contains "{err_msg:MaybeString}"' +) +def check_error_if_matching(context, err_msg: str = None): + if len(err_msg) > 0: + assert err_msg in context.sanity_check_err + else: + assert len(context.sanity_check_err) == 0 diff --git a/tests/unit.tags b/tests/unit.tags new file mode 100644 index 00000000..f6f3cad2 --- /dev/null +++ b/tests/unit.tags @@ -0,0 +1,26 @@ +@unit.abijson +@unit.abijson.byname +@unit.algod +@unit.algod.ledger_refactoring +@unit.applications +@unit.atc_method_args +@unit.atomic_transaction_composer +@unit.dryrun +@unit.dryrun.trace.application +@unit.feetest +@unit.indexer +@unit.indexer.ledger_refactoring +@unit.indexer.logs +@unit.offline +@unit.program_sanity_check +@unit.rekey +@unit.responses +@unit.responses.231 +@unit.responses.unlimited_assets +@unit.sourcemap +@unit.tealsign +@unit.transactions +@unit.transactions.keyreg +@unit.transactions.payment +@unit.stateproof.paths +@unit.stateproof.responses diff --git a/tests/unit_tests/test_other.py b/tests/unit_tests/test_other.py index 22daadcd..bff37092 100644 --- a/tests/unit_tests/test_other.py +++ b/tests/unit_tests/test_other.py @@ -248,6 +248,170 @@ def test_keyreg_txn_nonpart(self): encoding.msgpack_encode(encoding.future_msgpack_decode(keyregtxn)), ) + def test_stateproof_txn(self): + stateprooftxn = ( + "gqNzaWfEQHsgfEAmEHUxLLLR9s+Y/yq5WeoGo/jAArCbany+7ZYwExMySzAhmV7M7S8" + "+LBtJalB4EhzEUMKmt3kNKk6+vAWjdHhuh6Jmds0CaaJnaMQgSGO1GKSzyE7IEPItTx" + "CByw9x8FmnrCDexi9/cOUJOiKibHbNBlGjc25kxCC7PFJiqdXHTSAn46fq5Nb/cM9sT" + "OTF4FfBHtOblTRCBaJzcIahUIKjaHNogaF0AaJ0ZAGhU4KjaHNogaF0AaJ0ZAGhY8RA" + "dWo+1yk/97WVvXRuLLyywild8Xe6PxjtmuB/lShfdOXs0Au7Q67KkT5LzC88hX5fFvj" + "Bx/AqKREhoEd14JiTt6JwctwAlAAAAAAAAAAAAQAAAAABAQAAAAAAAAAAAAAAAQAAAA" + "AAAAEAAAAAAAAAAAAAAQAAAAABAAABAQEAAAAAAAABAAEAAAAAAAEAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAEAAQABAAEBAAABAAAAAAAAAAEAAAAAAQAAAAABAAAA" + "AAAAAAEAAAAAAAAAAQAAAAChcoKhMIKhcIKhcIKjY210xECJ3vxgEUPuCXmKeJQmasV" + "fhmraJZoVu+p8MTJc6EzU4PGZWuxd6DU80//HXDfVCImipmSQhSwkNEmea+Pg0lMwom" + "xmzQEAoXfPAA41/zsxyAChc4Ghc4SjaWR4AqNwcmaDo2hzaIGhdAGjcHRolNlYeEFKc" + "TJhL1ZITzA0NG1iYjBmZlZpeDE4dk5jYUdZNDFWbDh0VytIVDEzcEJmcDlTdjVWaitz" + "SHFSTUs5OW9GNU53SWlFM2ljRGRoNlhZL2d1eHRNdXc9PdlYZkVRTzFvSmpBNXZiSmZ" + "MT2JIQlNNQ3pqYkNDODZ1aVlJNUNDdW9yaDdFNEJlRTlSZFVjYmRqVkE1VWFDOHIzan" + "padjlseU95UmVVdjZVQUluN283cWc9PdlYNTNnZDBuM09LVU9yYXJNcG1SdnJ5UE8wc" + "Vpqb3V5c1BPVHVuQ2NTUnZRdG1BR1N6dllDWUZqYmxGZXZ0c3JQVUtYbnI0cGFWZXcy" + "V21Hb2tZSFVXOVE9PdlYZUZtME5iQlpaYlp0NzliS3JhblU5UUFCUnFldVhoOEk4Ry9" + "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" + "p50bY56ECrkenbz93WTvL89IXOLD4EDcYAycsZRCTH9E8iEdGQRKbm4Ks962KRFBUnM" + "NqxLUURUMUjugUMIrnUGuXGg6MJTXCh3mn+TNl1mKGQn3fZ7ksFo9ND+ZTFSlwOE728" + "cQ2H+Ao9quqYxvdmIWEDeuVDCVSbJYptNoHsu2fxiI4fRDRQNdUuTwypiBgQa6NaunL" + "aAaA4Bs8r7sYURv6j0aJEYhishCaL6KNqrF+DYRJPZyiYcWJMHL0yPISAk9KYCW9gtM" + "2IEfp8R5UliCvt3pEQyD5vXZthr89CNAiQElwMdCAA8iULb+8KoXBVriAFdm7M+HiLg" + "7TZJbsZFoN1XerqvoyTOmIT0vKUcYLuMOOBzAUd4oGENxzWDhV1eO4sTzvaD9tEFO9c" + "6k/Bx82BAC6HlBRVXLXJOxMPpNmqCpxKrE18bBAc9dfjkkS80rtJkBJ0opIzQx8YEoH" + "QQcxstQa5L1UUbYGmpnqIGZY9XKwC4mYFBEtJxIM0RE8y2Ra0FJcIMeHQ8pJpjybW69" + "XfIY+gBjona956scDOp10u/wPytquIjvOwFUM47Gm5cqS0QwUWMz1d/WCVwF627hlbw" + "QDKis6EG54MnJlA/oOrCxEGiBZpusf7LB2vUWzd2nd23ZFzp6U4oQR7D0AKllo7tQgk" + "pDVe0jgbp3CjJCX6T4CcFGX3tv0BvhtKp7Qk7gaScuZaExgqFwgqFwgqNjbXTEQNkzH" + "iRX8wGA8aHIiiQS5KYeZps0Ek70Cb5dfULAvUKx/Zlienia5CW4EaV3JTD06XCsQKpw" + "hgbahcYTgjzNVg+ibGbNAQChd88AA41/zsxyAKFzgqFszwAONf87McgAoXOEo2lkeAK" + "jcHJmg6Noc2iBoXQBo3B0aJTZWDFIZ3d6d0VibUxCdEU2RGx3RVZwd0hyTTNhMEY1UW" + "13aVFNdklqTndrdHJleXJwNW5Cc0xCcGlsSTJ3OW5DWERYVDlobW1SNFFRNG9Fcm51Z" + "XN5S2lRPT3ZWERmTW91R0dkNUZUNlFJZkE5cXF6ci9vNDBRYWNzc25vdGh0RncvUHo4" + "MHFRQ3QxUG1nNWFCZjZOcnhaLzRMaEtSb1I1Q2hQQitPVW5nOGNtZGRZQnVnPT3ZWDV" + "UMEk2UDBHSHY5cm1vTlVteC9jMXllY3QwMlFvR3UxLy9sSFYyZEhxTHVEaXFmeTVrak" + "1VU2xBR1Q1YSs3bzdiZzhDYk5VMnlpUmVIMjhRNnZQcWNBPT3ZWGhYT05jQVZ0cHpCe" + "DR0dHE4ZVJOWUhkNy9TcmEzSEJQd01HR1hXcnNSNEtxVTdtRkk5UURpSEU2MDlMd0RE" + "Nk1DYktXNWt2VnJsSVlzRWgvRWxMcERnPT2idGQEo3NpZ8UEzroApUZQwaeHxrhrTqd" + "7aJVAazbex5N6mFD56ayeJPkqFA2BUMwvda3qNqG4vs+3pQ7KuWR2MtDCvYMS7cio1v" + "sDCGv0cLIV0o0cOkR9Ehad20bd8/JrY6BVpGWaiVf0bChExg7LvT00grUIu7WY2Be7W" + "0vh1KpN8d82dHSAiOG3lxm5ru2gbBdJCaujG+kOj7eodqeeL1+biXy5H7ieSUXUzx0O" + "E2bBJXxbbzJLHzQjNc7b/hRmi7FFh02Tswr9OJf40mjKJJooMcTHVWJvhg1YPX0Mc3h" + "c/Eud+5SQF+9qVPh/Gf9PKo8ArnG1kGuUZJFJ7vFrQbpELVg2e1y/xspDWvVBWtoiMf" + "E1mO9tHWYoWqVsqabzYYJ+kOd/t5PfKQ+v+eqt6NHDWRt5rbqUIWIaZOXl6kI3o/+cZ" + "RhVPhMEZLSFzhz4eRFpzor3vu9B0MGus1fJXhKLy6PXjtWUkXHZ9ZLedL8Z5xzFPO4y" + "ORJi9AuxZXwyVHorRvBNLFzycw7F17eGw4fhj8vkx+pCkEHNITOeQ95JIKghG4iSL2+" + "bvLBxDcwqx6UmyMT1cQa7dM9kCTIYQDINApTbUqAW1JJAs9PHfkUMSqYQlD4SUQqDo9" + "mxmhf+ad96ajp24efjStEzRM16LdArEu6PMo8BdY0319XYnWK2EGXiS8n6k8XmTiNFT" + "Zk2Rp1R2STrZi9izpaGRcv2/uMY5xas6cO7EGhKXTcf6VuWRc1ZVGiuFGy6rSaD4aoY" + "m9eiQdEgylsPm5fg+jLavD2FkSdwY40LawWk1fKgPclZhVJkKJKxw2EQKgXNtbRA87v" + "tY5zXvY28tgLFvocXSS+iuHGuHUmtdLutdM5Qyi9amFRvDlfj8EldYrnMdWLwbIf2Jx" + "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" + "c3RwZg==" + ) + + self.assertEqual( + stateprooftxn, + encoding.msgpack_encode( + encoding.future_msgpack_decode(stateprooftxn) + ), + ) + def test_asset_create(self): golden = ( "gqNzaWfEQEDd1OMRoQI/rzNlU4iiF50XQXmup3k5czI9hEsNqHT7K4KsfmA/0DUVk"