Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new step asserting that AtomicTransactionComposer's attempt to add a method can fail with a particular error #347

Merged
merged 7 commits into from
Jun 17, 2022
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
UNITS = "@unit.abijson or @unit.abijson.byname or @unit.algod or @unit.algod.ledger_refactoring or @unit.applications 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"
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"
unit:
behave --tags=$(UNITS) test -f progress2

Expand Down
141 changes: 103 additions & 38 deletions test/steps/application_v2_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@

from algosdk import abi, atomic_transaction_composer, encoding, mnemonic
from algosdk.abi.contract import NetworkInfo
from algosdk.error import ABITypeError, IndexerHTTPError
from algosdk.error import (
ABITypeError,
IndexerHTTPError,
AtomicTransactionComposerError,
)
from algosdk.future import transaction

from test.steps.other_v2_steps import read_program
Expand Down Expand Up @@ -85,16 +89,32 @@ def s512_256_uint64(witness):
return int.from_bytes(encoding.checksum(witness)[:8], "big")


@step(
Copy link
Contributor Author

@tzaffi tzaffi Jun 14, 2022

Choose a reason for hiding this comment

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

moved unchanged from other_v2_steps after I realized that this is meant for application transactions

'I sign and submit the transaction, saving the txid. If there is an error it is "{error_string:MaybeString}".'
)
def sign_submit_save_txid_with_error(context, error_string):
try:
signed_app_transaction = context.app_transaction.sign(
context.transient_sk
)
context.app_txid = context.app_acl.send_transaction(
signed_app_transaction
)
except Exception as e:
if not error_string or error_string not in str(e):
raise RuntimeError(
"error string "
+ error_string
+ " not in actual error "
+ str(e)
)


@when("we make a GetApplicationByID call for applicationID {app_id}")
def application_info(context, app_id):
context.response = context.acl.application_info(int(app_id))


@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(
'we make a LookupApplicationLogsByID call with applicationID {app_id} limit {limit} minRound {min_round} maxRound {max_round} nextToken "{next_token:MaybeString}" sender "{sender:MaybeString}" and txID "{txid:MaybeString}"'
)
Expand Down Expand Up @@ -159,8 +179,13 @@ def lookup_application_include_all2(
context.response = json.loads(str(e))


@when("we make a LookupApplications call with applicationID {app_id}")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved closer to similar function

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_application(context, indexer, application_id):
def lookup_application2(context, indexer, application_id):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed to avoid method name collision

context.response = context.icls[indexer].applications(
application_id=int(application_id)
)
Expand Down Expand Up @@ -514,33 +539,33 @@ def add_transaction_to_composer(context):

def process_abi_args(context, method, arg_tokens):
method_args = []
for arg_index, arg in enumerate(method.args):
# Skip arg if it does not have a type
for arg_index, arg_token in enumerate(arg_tokens):
if arg_index >= len(method.args):
method_args.append(arg_token)
continue

arg = method.args[arg_index]
if isinstance(arg.type, abi.ABIType):
method_arg = arg.type.decode(
base64.b64decode(arg_tokens[arg_index])
)
method_arg = arg.type.decode(base64.b64decode(arg_token))
method_args.append(method_arg)
elif arg.type == abi.ABIReferenceType.ACCOUNT:
method_arg = abi.AddressType().decode(
base64.b64decode(arg_tokens[arg_index])
)
method_arg = abi.AddressType().decode(base64.b64decode(arg_token))
method_args.append(method_arg)
elif (
arg.type == abi.ABIReferenceType.APPLICATION
or arg.type == abi.ABIReferenceType.ASSET
):
parts = arg_tokens[arg_index].split(":")
parts = arg_token.split(":")
if len(parts) == 2 and parts[0] == "ctxAppIdx":
method_arg = context.app_ids[int(parts[1])]
else:
method_arg = abi.UintType(64).decode(
base64.b64decode(arg_tokens[arg_index])
base64.b64decode(arg_token)
)
method_args.append(method_arg)
else:
# Append the transaction signer as is
method_args.append(arg_tokens[arg_index])
method_args.append(arg_token)
return method_args


Expand All @@ -561,7 +586,7 @@ def append_txn_to_method_args(context):
)
def append_app_args_to_method_args(context, method_args):
# Returns a list of ABI method arguments
app_args = method_args.split(",")
app_args = method_args.split(",") if method_args else []
context.method_args += app_args


Expand All @@ -583,6 +608,7 @@ def abi_method_adder(
local_ints=None,
extra_pages=None,
force_unique_transactions=False,
exception_key="none",
):
if account_type == "transient":
sender = context.transient_pk
Expand Down Expand Up @@ -621,39 +647,66 @@ def int_if_given(given):
app_args = process_abi_args(
context, context.abi_method, context.method_args
)
context.app_args = app_args
note = None
if force_unique_transactions:
note = (
b"I should be unique thanks to this nonce: "
+ context.nonce.encode()
)

context.atomic_transaction_composer.add_method_call(
app_id=app_id,
method=context.abi_method,
sender=sender,
sp=context.suggested_params,
signer=context.transaction_signer,
method_args=app_args,
on_complete=operation_string_to_enum(operation),
local_schema=local_schema,
global_schema=global_schema,
approval_program=approval_program,
clear_program=clear_program,
extra_pages=extra_pages,
note=note,
)
try:
context.atomic_transaction_composer.add_method_call(
app_id=app_id,
method=context.abi_method,
sender=sender,
sp=context.suggested_params,
signer=context.transaction_signer,
method_args=app_args,
on_complete=operation_string_to_enum(operation),
local_schema=local_schema,
global_schema=global_schema,
approval_program=approval_program,
clear_program=clear_program,
extra_pages=extra_pages,
note=note,
)
except AtomicTransactionComposerError as atce:
assert (
exception_key != "none"
), f"cucumber step asserted that no exception resulted, but the following exception actually occurred: {atce}"

arglen_exception = "argument_count_mismatch"
known_exception_keys = [arglen_exception]
assert (
exception_key in known_exception_keys
), f"encountered exception key '{exception_key}' which is not in known set: {known_exception_keys}"

if exception_key == arglen_exception:
exception_msg = (
"number of method arguments do not match the method signature"
)
assert exception_msg in str(
atce
), f"expected argument count mismatch error such as '{exception_msg}' but got the following instead: {atce}"
return

assert (
exception_key == "none"
), f"should have encountered an AtomicTransactionComposerError keyed by '{exception_key}', but no such exception has been detected"


@step(
'I add a nonced method call with the {account_type} account, the current application, suggested params, on complete "{operation}", current transaction signer, current method arguments.'
'I add a method call with the {account_type} account, the current application, suggested params, on complete "{operation}", current transaction signer, current method arguments; any resulting exception has key "{exception_key}".'
)
def add_abi_method_call_nonced(context, account_type, operation):
def add_abi_method_call_with_exception(
context, account_type, operation, exception_key
):
abi_method_adder(
context,
account_type,
operation,
force_unique_transactions=True,
exception_key=exception_key,
)


Expand Down Expand Up @@ -718,6 +771,18 @@ def add_abi_method_call_creation(
)


@step(
'I add a nonced method call with the {account_type} account, the current application, suggested params, on complete "{operation}", current transaction signer, current method arguments.'
)
def add_abi_method_call_nonced(context, account_type, operation):
abi_method_adder(
context,
account_type,
operation,
force_unique_transactions=True,
)


@step(
'I build the transaction group with the composer. If there is an error it is "{error_string:MaybeString}".'
)
Expand Down Expand Up @@ -827,7 +892,7 @@ def serialize_method_to_json(context):
@when(
'I create the Method object with name "{method_name}" method description "{method_desc}" first argument type "{first_arg_type}" first argument description "{first_arg_desc}" second argument type "{second_arg_type}" second argument description "{second_arg_desc}" and return type "{return_arg_type}"'
)
def create_method_from_test_with_arg_name(
def create_method_from_test_with_arg_name_and_desc(
context,
method_name,
method_desc,
Expand Down
23 changes: 1 addition & 22 deletions test/steps/other_v2_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ def txns_by_addr(
@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}'
)
def txns_by_addr(
def txns_by_addr2(
context,
account,
notePrefixB64,
Expand Down Expand Up @@ -1406,27 +1406,6 @@ def algod_v2_client(context):
context.app_acl = algod.AlgodClient(daemon_token, algod_address)


@step(
'I sign and submit the transaction, saving the txid. If there is an error it is "{error_string:MaybeString}".'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved this to application_v2_steps.py

)
def sign_submit_save_txid_with_error(context, error_string):
try:
signed_app_transaction = context.app_transaction.sign(
context.transient_sk
)
context.app_txid = context.app_acl.send_transaction(
signed_app_transaction
)
except Exception as e:
if not error_string or error_string not in str(e):
raise RuntimeError(
"error string "
+ error_string
+ " not in actual error "
+ str(e)
)


@when('I compile a teal program "{program}"')
def compile_step(context, program):
data = load_resource(program)
Expand Down