diff --git a/Makefile b/Makefile index b2c41234..730bdeed 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ +UNITS = "@unit.abijson or @unit.algod or @unit.applications or @unit.atomic_transaction_composer or @unit.dryrun or @unit.feetest or @unit.indexer or @unit.indexer.logs or @unit.offline or @unit.rekey or @unit.responses or @unit.responses.231 or @unit.tealsign or @unit.transactions or @unit.transactions.payment" unit: - behave --tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.tealsign or @unit.dryrun or @unit.applications or @unit.responses or @unit.transactions or @unit.transactions.payment or @unit.responses.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.atomic_transaction_composer" test -f progress2 + behave --tags=$(UNITS) test -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 or @send" integration: - behave --tags="@algod or @assets or @auction or @kmd or @send or @template or @indexer or @indexer.applications or @rekey or @compile or @dryrun or @dryrun.testing or @applications or @applications.verified or @indexer.231 or @abi" test -f progress2 + behave --tags=$(INTEGRATIONS) test -f progress2 docker-test: ./run_integration.sh diff --git a/README.md b/README.md index 44f67cd1..41436ead 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,31 @@ # py-algorand-sdk -[![Build Status](https://travis-ci.com/algorand/py-algorand-sdk.svg?branch=master)](https://travis-ci.com/algorand/py-algorand-sdk) -[![PyPI version](https://badge.fury.io/py/py-algorand-sdk.svg)](https://badge.fury.io/py/py-algorand-sdk) -[![Documentation Status](https://readthedocs.org/projects/py-algorand-sdk/badge/?version=latest&style=flat)](https://py-algorand-sdk.readthedocs.io/en/latest) + +[![Build Status](https://travis-ci.com/algorand/py-algorand-sdk.svg?branch=master)](https://travis-ci.com/algorand/py-algorand-sdk) +[![PyPI version](https://badge.fury.io/py/py-algorand-sdk.svg)](https://badge.fury.io/py/py-algorand-sdk) +[![Documentation Status](https://readthedocs.org/projects/py-algorand-sdk/badge/?version=latest&style=flat)](https://py-algorand-sdk.readthedocs.io/en/latest) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) A python library for interacting with the Algorand network. ## Installation -Run ```$ pip3 install py-algorand-sdk``` to install the package. +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]```. +Alternatively, choose a [distribution file](https://pypi.org/project/py-algorand-sdk/#files), and run `$ pip3 install [file name]`. ## SDK Development Install dependencies -* `pip install -r requirements.txt` + +- `pip install -r requirements.txt` Run tests -* `make docker-test` + +- `make docker-test` Format code: -* `black .` + +- `black .` ## Quick start @@ -50,18 +54,18 @@ Follow the instructions in Algorand's [developer resources](https://developer.al Before running [example.py](https://github.com/algorand/py-algorand-sdk/blob/master/examples/example.py), start kmd on a private network or testnet node: -``` -$ ./goal kmd start -d [data directory] +```bash +./goal kmd start -d [data directory] ``` Next, create a wallet and an account: -``` -$ ./goal wallet new [wallet name] -d [data directory] +```bash +./goal wallet new [wallet name] -d [data directory] ``` -``` -$ ./goal account new -d [data directory] -w [wallet name] +```bash +./goal account new -d [data directory] -w [wallet name] ``` Visit the [Algorand dispenser](https://bank.testnet.algorand.network/) and enter the account address to fund your account. @@ -71,7 +75,9 @@ Next, in [tokens.py](https://github.com/algorand/py-algorand-sdk/blob/master/exa You're now ready to run example.py! ## Documentation + Documentation for the Python SDK is available at [py-algorand-sdk.readthedocs.io](https://py-algorand-sdk.readthedocs.io/en/latest/). ## License -py-algorand-sdk is licensed under a MIT license. See the [LICENSE](https://github.com/algorand/py-algorand-sdk/blob/master/LICENSE) file for details. + +py-algorand-sdk is licensed under an MIT license. See the [LICENSE](https://github.com/algorand/py-algorand-sdk/blob/master/LICENSE) file for details. diff --git a/algosdk/atomic_transaction_composer.py b/algosdk/atomic_transaction_composer.py index df6095cf..a4d29c1a 100644 --- a/algosdk/atomic_transaction_composer.py +++ b/algosdk/atomic_transaction_composer.py @@ -495,26 +495,27 @@ def execute( raw_value = None return_value = None decode_error = None + tx_info = None if i not in self.method_dict: continue - # Return is void - if self.method_dict[i].returns.type == abi.Returns.VOID: - method_results.append( - ABIResult( - tx_id=tx_id, - raw_value=raw_value, - return_value=return_value, - decode_error=decode_error, - ) - ) - continue # Parse log for ABI method return value try: - resp = client.pending_transaction_info(tx_id) - confirmed_round = resp["confirmed-round"] - logs = resp["logs"] if "logs" in resp else [] + tx_info = client.pending_transaction_info(tx_id) + if self.method_dict[i].returns.type == abi.Returns.VOID: + method_results.append( + ABIResult( + tx_id=tx_id, + raw_value=raw_value, + return_value=return_value, + decode_error=decode_error, + tx_info=tx_info, + ) + ) + continue + + logs = tx_info["logs"] if "logs" in tx_info else [] # Look for the last returned value in the log if not logs: @@ -543,6 +544,7 @@ def execute( raw_value=raw_value, return_value=return_value, decode_error=decode_error, + tx_info=tx_info, ) method_results.append(abi_result) @@ -689,16 +691,18 @@ def __init__( raw_value: bytes, return_value: Any, decode_error: error, + tx_info: dict, ) -> None: self.tx_id = tx_id self.raw_value = raw_value self.return_value = return_value self.decode_error = decode_error + self.tx_info = tx_info class AtomicTransactionResponse: def __init__( - self, confirmed_round: int, tx_ids: List[str], results: ABIResult + self, confirmed_round: int, tx_ids: List[str], results: List[ABIResult] ) -> None: self.confirmed_round = confirmed_round self.tx_ids = tx_ids diff --git a/algosdk/future/template.py b/algosdk/future/template.py index 4efd4237..77b424c1 100644 --- a/algosdk/future/template.py +++ b/algosdk/future/template.py @@ -7,6 +7,10 @@ class Template: + """ + NOTE: This class is deprecated + """ + def get_address(self): """ Return the address of the contract. @@ -19,6 +23,8 @@ def get_program(self): class Split(Template): """ + NOTE: This class is deprecated. + Split allows locking algos in an account which allows transfering to two predefined addresses in a specified ratio such that for the given ratn and ratd parameters we have: @@ -156,6 +162,8 @@ def get_split_funds_transaction(contract, amount: int, sp): class HTLC(Template): """ + NOTE: This class is deprecated. + Hash Time Locked Contract allows a user to recieve the Algo prior to a deadline (in terms of a round) by proving knowledge of a special value or to forfeit the ability to claim, returning it to the payer. @@ -291,6 +299,8 @@ def get_transaction(contract, preimage, sp): class DynamicFee(Template): """ + NOTE: This class is deprecated. + DynamicFee contract allows you to create a transaction without specifying the fee. The fee will be determined at the moment of transfer. @@ -416,6 +426,8 @@ def sign_dynamic_fee(self, private_key): class PeriodicPayment(Template): """ + NOTE: This class is deprecated. + PeriodicPayment contract enables creating an account which allows the withdrawal of a fixed amount of assets every fixed number of rounds to a specific Algrorand Address. In addition, the contract allows to add @@ -528,6 +540,8 @@ def get_withdrawal_transaction(contract, sp): class LimitOrder(Template): """ + NOTE: This class is deprecated. + Limit Order allows to trade Algos for other assets given a specific ratio; for N Algos, swap for Rate * N Assets. ... diff --git a/algosdk/logic.py b/algosdk/logic.py index 7cdf96a6..10c8c573 100644 --- a/algosdk/logic.py +++ b/algosdk/logic.py @@ -278,6 +278,12 @@ def get_application_address(appID: int) -> str: Returns: str: The address corresponding to that application's escrow account. """ + assert isinstance( + appID, int + ), "(Expected an int for appID but got [{}] which has type [{}])".format( + appID, type(appID) + ) + to_sign = constants.APPID_PREFIX + appID.to_bytes(8, "big") checksum = encoding.checksum(to_sign) return encoding.encode_address(checksum) diff --git a/algosdk/v2client/algod.py b/algosdk/v2client/algod.py index 44f1e03c..89513b40 100644 --- a/algosdk/v2client/algod.py +++ b/algosdk/v2client/algod.py @@ -1,14 +1,10 @@ -from urllib.request import Request, urlopen +import base64 +import json from urllib import parse import urllib.error -import json -import base64 -from .. import error -from .. import encoding -from .. import constants -from .. import future -import msgpack -from .. import util +from urllib.request import Request, urlopen + +from .. import constants, encoding, error, future, logic, util api_version_path_prefix = "/v2" diff --git a/requirements.txt b/requirements.txt index 4b7551fd..f9f9dfee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ . black==21.9b0 +glom==20.11.0 +pytest==6.2.5 git+https://github.com/behave/behave diff --git a/run_integration.sh b/run_integration.sh index 1d4861b0..8693a652 100755 --- a/run_integration.sh +++ b/run_integration.sh @@ -7,7 +7,7 @@ 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 +git clone --single-branch --branch zc2c https://github.com/algorand/algorand-sdk-testing.git test-harness ## Copy feature files into the project resources mkdir -p test/features diff --git a/test/environment.py b/test/environment.py index 802ce63f..3db62a4c 100644 --- a/test/environment.py +++ b/test/environment.py @@ -32,7 +32,7 @@ def encode_bytes(d): encode_bytes(d[i]) else: if isinstance(d[i], bytes): - d[i] = base64.b64encode(v).decode() + d[i] = base64.b64encode(d[i]).decode() return d diff --git a/test/steps/__init__.py b/test/steps/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/steps/steps.py b/test/steps/steps.py index ded715ce..3e777230 100644 --- a/test/steps/steps.py +++ b/test/steps/steps.py @@ -941,162 +941,6 @@ def revoke_txn(context, amount): ) -@given( - "a split contract with ratio {ratn} to {ratd} and minimum payment {min_pay}" -) -def split_contract(context, ratn, ratd, min_pay): - context.params = context.acl.suggested_params_as_object() - context.template = template.Split( - context.accounts[0], - context.accounts[1], - context.accounts[2], - int(ratn), - int(ratd), - context.params.last, - int(min_pay), - 20000, - ) - context.fund_amt = int( - 2 * context.template.min_pay * (int(ratn) + int(ratd)) / int(ratn) - ) - - -@when("I send the split transactions") -def send_split(context): - amt = context.fund_amt // 2 - txns = context.template.get_split_funds_transaction( - context.template.get_program(), amt, context.params - ) - context.txn = txns[0].transaction - context.acl.send_transactions(txns) - - -@given('an HTLC contract with hash preimage "{preimage}"') -def htlc_contract(context, preimage): - context.preimage = bytes(preimage, "ascii") - context.params = context.acl.suggested_params_as_object() - h = base64.b64encode(hashlib.sha256(context.preimage).digest()).decode() - context.fund_amt = 1000000 - context.template = template.HTLC( - context.accounts[0], - context.accounts[1], - "sha256", - h, - context.params.last, - 2000, - ) - - -@when("I fund the contract account") -def fund_contract(context): - context.txn = transaction.PaymentTxn( - context.accounts[0], - context.params, - context.template.get_address(), - context.fund_amt, - ) - context.txn = context.wallet.sign_transaction(context.txn) - context.acl.send_transaction(context.txn) - transaction.wait_for_confirmation(context.acl, context.txn.get_txid(), 10) - - -@when("I claim the algos") -def claim_algos(context): - context.ltxn = template.HTLC.get_transaction( - context.template.get_program(), - base64.b64encode(context.preimage), - context.params, - ) - context.txn = context.ltxn.transaction - context.acl.send_transaction(context.ltxn) - - -@given( - "a periodic payment contract with withdrawing window {wd_window} and period {period}" -) -def periodic_pay_contract(context, wd_window, period): - context.params = context.acl.suggested_params_as_object() - context.template = template.PeriodicPayment( - context.accounts[1], - 12345, - int(wd_window), - int(period), - 2000, - int(context.params.last), - ) - context.fund_amt = 1000000 - - -@when("I claim the periodic payment") -def claim_periodic(context): - context.params.first = ( - context.params.first - // context.template.period - * context.template.period - ) - ltxn = context.template.get_withdrawal_transaction( - context.template.get_program(), context.params - ) - context.txn = ltxn.transaction - context.acl.send_transaction(ltxn) - - -@given("contract test fixture") -def contract_fixture(context): - pass - - -@given("a limit order contract with parameters {ratn} {ratd} {min_trade}") -def limit_order_contract(context, ratn, ratd, min_trade): - context.params = context.acl.suggested_params_as_object() - context.ratn = int(ratn) - context.ratd = int(ratd) - context.template = template.LimitOrder( - context.accounts[1], - context.asset_index, - int(ratn), - int(ratd), - context.params.last, - 2000, - int(min_trade), - ) - context.sk = context.wallet.export_key(context.accounts[0]) - context.fund_amt = max(2 * int(min_trade), 1000000) - context.rcv = context.accounts[1] - - -@when("I swap assets for algos") -def swap_assets(context): - context.txns = context.template.get_swap_assets_transactions( - context.template.get_program(), - 12345, - int(12345 * context.ratd / context.ratn), - context.sk, - context.params, - ) - context.txn = context.txns[0].transaction - context.acl.send_transactions(context.txns) - - -@given("a dynamic fee contract with amount {amt}") -def dynamic_fee_contract(context, amt): - context.params = context.acl.suggested_params_as_object() - context.sk = context.wallet.export_key(context.accounts[0]) - context.template = template.DynamicFee( - context.accounts[1], int(amt), context.params - ) - txn, lsig = context.template.sign_dynamic_fee(context.sk) - context.txns = context.template.get_transactions( - txn, lsig, context.wallet.export_key(context.accounts[2]), 0 - ) - context.txn = context.txns[0].transaction - - -@when("I send the dynamic fee transactions") -def send_dynamic_fee(context): - context.acl.send_transactions(context.txns) - - @given("I sign the transaction with the private key") def given_sign_with_sk(context): # python cucumber considers "Given foo" and "When foo" to be distinct, diff --git a/test/steps/v2_steps.py b/test/steps/v2_steps.py index f7b6fd96..35a8db7f 100644 --- a/test/steps/v2_steps.py +++ b/test/steps/v2_steps.py @@ -1,13 +1,17 @@ import base64 import json import os +import re import urllib import unittest from datetime import datetime +from pathlib import Path +import pytest +from typing import List, Union from urllib.request import Request, urlopen -from algosdk.abi.contract import NetworkInfo -import parse +# TODO: This file is WAY TOO BIG. Break it up into logically related chunks. + from behave import ( given, when, @@ -16,24 +20,28 @@ step, ) # pylint: disable=no-name-in-module -from algosdk.future import transaction +from glom import glom +import parse + from algosdk import ( abi, account, atomic_transaction_composer, encoding, error, + logic, mnemonic, ) +from algosdk.abi.contract import NetworkInfo +from algosdk.error import ABITypeError, AlgodHTTPError, IndexerHTTPError +from algosdk.future import transaction from algosdk.v2client import * from algosdk.v2client.models import ( DryrunRequest, DryrunSource, Account, - Application, ApplicationLocalState, ) -from algosdk.error import AlgodHTTPError, IndexerHTTPError from algosdk.testing.dryrun import DryrunTestCaseMixin from test.steps.steps import token as daemon_token @@ -1710,8 +1718,12 @@ def split_and_process_app_args(in_args): sub_args = [sub_arg.split(":") for sub_arg in split_args] app_args = [] for sub_arg in sub_args: - if sub_arg[0] == "str": + if len(sub_arg) == 1: # assume int + app_args.append(int(sub_arg[0])) + elif sub_arg[0] == "str": app_args.append(bytes(sub_arg[1], "ascii")) + elif sub_arg[0] == "b64": + app_args.append(base64.decodebytes(sub_arg[1].encode())) elif sub_arg[0] == "int": app_args.append(int(sub_arg[1])) elif sub_arg[0] == "addr": @@ -1770,22 +1782,14 @@ def build_app_transaction( operation = operation_string_to_enum(operation) if sender == "none": sender = None - dir_path = os.path.dirname(os.path.realpath(__file__)) - dir_path = os.path.dirname(os.path.dirname(dir_path)) if approval_program == "none": approval_program = None elif approval_program: - with open( - dir_path + "/test/features/resources/" + approval_program, "rb" - ) as f: - approval_program = bytearray(f.read()) + approval_program = read_program(context, approval_program) if clear_program == "none": clear_program = None elif clear_program: - with open( - dir_path + "/test/features/resources/" + clear_program, "rb" - ) as f: - clear_program = bytearray(f.read()) + clear_program = read_program(context, clear_program) if app_args == "none": app_args = None elif app_args: @@ -1927,22 +1931,14 @@ def build_app_txn_with_transient( ): application_id = context.current_application_id operation = operation_string_to_enum(operation) - dir_path = os.path.dirname(os.path.realpath(__file__)) - dir_path = os.path.dirname(os.path.dirname(dir_path)) if approval_program == "none": approval_program = None elif approval_program: - with open( - dir_path + "/test/features/resources/" + approval_program, "rb" - ) as f: - approval_program = bytearray(f.read()) + approval_program = read_program(context, approval_program) if clear_program == "none": clear_program = None elif clear_program: - with open( - dir_path + "/test/features/resources/" + clear_program, "rb" - ) as f: - clear_program = bytearray(f.read()) + clear_program = read_program(context, clear_program) local_schema = transaction.StateSchema( num_uints=int(local_ints), num_byte_slices=int(local_bytes) ) @@ -2026,18 +2022,67 @@ def wait_for_app_txn_confirm(context): ) -@given("I remember the new application ID.") +@given("I reset the array of application IDs to remember.") +def reset_appid_list(context): + context.app_ids = [] + + +@step("I remember the new application ID.") def remember_app_id(context): if hasattr(context, "acl"): - context.current_application_id = context.acl.pending_transaction_info( - context.app_txid - )["txresults"]["createdapp"] + app_id = context.acl.pending_transaction_info(context.app_txid)[ + "txresults" + ]["createdapp"] else: - context.current_application_id = ( - context.app_acl.pending_transaction_info(context.app_txid)[ - "application-index" - ] - ) + app_id = context.app_acl.pending_transaction_info(context.app_txid)[ + "application-index" + ] + + context.current_application_id = app_id + if not hasattr(context, "app_ids"): + context.app_ids = [] + + context.app_ids.append(app_id) + + +@then( + "I get the account address for the current application and see that it matches the app id's hash" +) +def assert_app_account_is_the_hash(context): + app_id = context.current_application_id + expected = encoding.encode_address( + encoding.checksum(b"appID" + app_id.to_bytes(8, "big")) + ) + actual = logic.get_application_address(app_id) + assert ( + expected == actual + ), f"account-address: expected [{expected}], but got [{actual}]" + + +def fund_account_address( + context, account_address: str, amount: Union[int, str] +): + sp = context.app_acl.suggested_params() + payment = transaction.PaymentTxn( + context.accounts[0], + sp, + account_address, + int(amount), + ) + signed_payment = context.wallet.sign_transaction(payment) + context.app_acl.send_transaction(signed_payment) + transaction.wait_for_confirmation(context.app_acl, payment.get_txid(), 10) + + +@given( + "I fund the current application's address with {fund_amount} microalgos." +) +def fund_app_account(context, fund_amount): + fund_account_address( + context, + logic.get_application_address(context.current_application_id), + fund_amount, + ) @given("an application id {app_id}") @@ -2116,15 +2161,36 @@ def verify_app_txn( assert found_value_for_key -def load_resource(res): +def load_resource(res, is_binary=True): """load data from features/resources""" - dir_path = os.path.dirname(os.path.realpath(__file__)) - path = os.path.join(dir_path, "..", "features", "resources", res) - with open(path, "rb") as fin: + path = Path(__file__).parent.parent / "features" / "resources" / res + filemode = "rb" if is_binary else "r" + with open(path, filemode) as fin: data = fin.read() return data +def read_program_binary(path): + return bytearray(load_resource(path)) + + +def read_program(context, path): + """ + Assumes that have already added `context.app_acl` so need to have previously + called one of the steps beginning with "Given an algod v2 client..." + """ + if path.endswith(".teal"): + assert hasattr( + context, "app_acl" + ), "Cannot compile teal program into binary because no algod v2 client has been provided in the context" + + teal = load_resource(path, is_binary=False) + resp = context.app_acl.compile(teal) + return base64.b64decode(resp["result"]) + + return read_program_binary(path) + + @when('I compile a teal program "{program}"') def compile_step(context, program): data = load_resource(program) @@ -2147,6 +2213,15 @@ def compile_check_step(context, status, result, hash): assert context.response["hash"] == hash +@then( + 'base64 decoding the response is the same as the binary "{binary:MaybeString}"' +) +def b64decode_compiled_teal_step(context, binary): + binary = load_resource(binary) + response_result = context.response["result"] + assert base64.b64decode(response_result.encode()) == binary + + @when('I dryrun a "{kind}" program "{program}"') def dryrun_step(context, kind, program): data = load_resource(program) @@ -2349,15 +2424,7 @@ def create_atomic_transaction_composer(context): context.method_list = [] -@given("I make a transaction signer for the transient account.") -def create_transient_transaction_signer(context): - private_key = context.transient_sk - context.transaction_signer = ( - atomic_transaction_composer.AccountTransactionSigner(private_key) - ) - - -@when("I make a transaction signer for the {account_type} account.") +@step("I make a transaction signer for the {account_type} account.") def create_transaction_signer(context, account_type): if account_type == "transient": private_key = context.transient_sk @@ -2396,7 +2463,7 @@ def add_transaction_to_composer(context): ) -def process_abi_args(method, arg_tokens): +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 @@ -2414,9 +2481,13 @@ def process_abi_args(method, arg_tokens): arg.type == abi.ABIReferenceType.APPLICATION or arg.type == abi.ABIReferenceType.ASSET ): - method_arg = abi.UintType(64).decode( - base64.b64decode(arg_tokens[arg_index]) - ) + parts = arg_tokens[arg_index].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]) + ) method_args.append(method_arg) else: # Append the transaction signer as is @@ -2445,10 +2516,25 @@ def append_app_args_to_method_args(context, method_args): context.method_args += app_args -@step( - 'I add a 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(context, account_type, operation): +@given('I add the nonce "{nonce}"') +def add_nonce(context, nonce): + context.nonce = nonce + + +def abi_method_adder( + context, + account_type, + operation, + create_when_calling=False, + approval_program_path=None, + clear_program_path=None, + global_bytes=None, + global_ints=None, + local_bytes=None, + local_ints=None, + extra_pages=None, + force_unique_transactions=False, +): if account_type == "transient": sender = context.transient_pk elif account_type == "signing": @@ -2457,22 +2543,86 @@ def add_abi_method_call(context, account_type, operation): raise NotImplementedError( "cannot make transaction signer for " + account_type ) - app_args = process_abi_args(context.abi_method, context.method_args) + approval_program = clear_program = None + global_schema = local_schema = None + + def int_if_given(given): + return int(given) if given else 0 + + local_schema = global_schema = None + if create_when_calling: + if approval_program_path: + approval_program = read_program(context, approval_program_path) + if clear_program_path: + clear_program = read_program(context, clear_program_path) + if local_ints or local_bytes: + local_schema = transaction.StateSchema( + num_uints=int_if_given(local_ints), + num_byte_slices=int_if_given(local_bytes), + ) + if global_ints or global_bytes: + global_schema = transaction.StateSchema( + num_uints=int_if_given(global_ints), + num_byte_slices=int_if_given(global_bytes), + ) + extra_pages = int_if_given(extra_pages) + + app_id = int(context.current_application_id) + + app_args = process_abi_args( + context, context.abi_method, context.method_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=int(context.current_application_id), + 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, + ) + + +@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 add a 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(context, account_type, operation): + abi_method_adder( + context, + account_type, + operation, ) @when( 'I add a method call with the {account_type} account, the current application, suggested params, on complete "{operation}", current transaction signer, current method arguments, approval-program "{approval_program_path:MaybeString}", clear-program "{clear_program_path:MaybeString}", global-bytes {global_bytes}, global-ints {global_ints}, local-bytes {local_bytes}, local-ints {local_ints}, extra-pages {extra_pages}.' ) -def add_abi_method_call_creation( +def add_abi_method_call_creation_with_allocs( context, account_type, operation, @@ -2484,52 +2634,18 @@ def add_abi_method_call_creation( local_ints, extra_pages, ): - if account_type == "transient": - sender = context.transient_pk - elif account_type == "signing": - sender = mnemonic.to_public_key(context.signing_mnemonic) - else: - raise NotImplementedError( - "cannot make transaction signer for " + account_type - ) - dir_path = os.path.dirname(os.path.realpath(__file__)) - dir_path = os.path.dirname(os.path.dirname(dir_path)) - if approval_program_path: - with open( - dir_path + "/test/features/resources/" + approval_program_path, - "rb", - ) as f: - approval_program = bytearray(f.read()) - else: - approval_program = None - if clear_program_path: - with open( - dir_path + "/test/features/resources/" + clear_program_path, "rb" - ) as f: - clear_program = bytearray(f.read()) - else: - clear_program = None - local_schema = transaction.StateSchema( - num_uints=int(local_ints), num_byte_slices=int(local_bytes) - ) - global_schema = transaction.StateSchema( - num_uints=int(global_ints), num_byte_slices=int(global_bytes) - ) - extra_pages = int(extra_pages) - app_args = process_abi_args(context.abi_method, context.method_args) - context.atomic_transaction_composer.add_method_call( - app_id=int(context.current_application_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, + abi_method_adder( + context, + account_type, + operation, + True, + approval_program_path, + clear_program_path, + global_bytes, + global_ints, + local_bytes, + local_ints, + extra_pages, ) @@ -2537,44 +2653,19 @@ def add_abi_method_call_creation( 'I add a method call with the {account_type} account, the current application, suggested params, on complete "{operation}", current transaction signer, current method arguments, approval-program "{approval_program_path:MaybeString}", clear-program "{clear_program_path:MaybeString}".' ) def add_abi_method_call_creation( - context, account_type, operation, approval_program_path, clear_program_path + context, + account_type, + operation, + approval_program_path, + clear_program_path, ): - if account_type == "transient": - sender = context.transient_pk - elif account_type == "signing": - sender = mnemonic.to_public_key(context.signing_mnemonic) - else: - raise NotImplementedError( - "cannot make transaction signer for " + account_type - ) - dir_path = os.path.dirname(os.path.realpath(__file__)) - dir_path = os.path.dirname(os.path.dirname(dir_path)) - if approval_program_path: - with open( - dir_path + "/test/features/resources/" + approval_program_path, - "rb", - ) as f: - approval_program = bytearray(f.read()) - else: - approval_program = None - if clear_program_path: - with open( - dir_path + "/test/features/resources/" + clear_program_path, "rb" - ) as f: - clear_program = bytearray(f.read()) - else: - clear_program = None - app_args = process_abi_args(context.abi_method, context.method_args) - context.atomic_transaction_composer.add_method_call( - app_id=int(context.current_application_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), - approval_program=approval_program, - clear_program=clear_program, + abi_method_adder( + context, + account_type, + operation, + True, + approval_program_path, + clear_program_path, ) @@ -2685,6 +2776,29 @@ def check_atomic_transaction_composer_response(context, returns): assert result.decode_error is None +@then('The app should have returned ABI types "{abiTypes:MaybeString}".') +def check_atomic_transaction_composer_return_type(context, abiTypes): + expected_tokens = abiTypes.split(":") + results = context.atomic_transaction_composer_return.abi_results + assert len(expected_tokens) == len( + results + ), f"surprisingly, we don't have the same number of expected results ({len(expected_tokens)}) as actual results ({len(results)})" + for i, expected in enumerate(expected_tokens): + result = results[i] + assert result.decode_error is None + + if expected == "void": + assert result.raw_value is None + with pytest.raises(ABITypeError): + abi.ABIType.from_string(expected) + continue + + expected_type = abi.ABIType.from_string(expected) + decoded_result = expected_type.decode(result.raw_value) + result_round_trip = expected_type.encode(decoded_result) + assert result_round_trip == result.raw_value + + @when("I serialize the Method object into json") def serialize_method_to_json(context): context.json_output = context.abi_method.dictify() @@ -2822,3 +2936,82 @@ def serialize_contract_to_json(context): def deserialize_json_to_contract(context): actual = abi.Contract.undictify(context.json_output) assert actual == context.abi_contract + + +@then( + 'I dig into the paths "{paths}" of the resulting atomic transaction tree I see group ids and they are all the same' +) +def same_groupids_for_paths(context, paths): + paths = [[int(p) for p in path.split(",")] for path in paths.split(":")] + grp = None + for path in paths: + d = context.atomic_transaction_composer_return.abi_results + for idx, p in enumerate(path): + d = d["inner-txns"][p] if idx else d[idx].tx_info + _grp = d["txn"]["txn"]["grp"] + if not grp: + grp = _grp + else: + assert grp == _grp, f"non-constant txn group hashes {_grp} v {grp}" + + +@then( + 'I can dig the {i}th atomic result with path "{path}" and see the value "{field}"' +) +def glom_app_eval_delta(context, i, path, field): + results = context.atomic_transaction_composer_return.abi_results + actual_field = glom(results[int(i)].tx_info, path) + assert field == str( + actual_field + ), f"path [{path}] expected value [{field}] but got [{actual_field}] instead" + + +def s512_256_uint64(witness): + return int.from_bytes(encoding.checksum(witness)[:8], "big") + + +@then( + "The {result_index}th atomic result for randomInt({input}) proves correct" +) +def sha512_256_of_witness_mod_n_is_result(context, result_index, input): + input = int(input) + abi_type = abi.ABIType.from_string("(uint64,byte[17])") + result = context.atomic_transaction_composer_return.abi_results[ + int(result_index) + ] + rand_int, witness = abi_type.decode(result.raw_value) + witness = bytes(witness) + x = s512_256_uint64(witness) + quotient = x % input + assert quotient == rand_int + + +@then( + 'The {result_index}th atomic result for randElement("{input}") proves correct' +) +def char_with_idx_sha512_256_of_witness_mod_n_is_result( + context, result_index, input +): + abi_type = abi.ABIType.from_string("(byte,byte[17])") + result = context.atomic_transaction_composer_return.abi_results[ + int(result_index) + ] + rand_elt, witness = abi_type.decode(result.raw_value) + witness = bytes(witness) + x = s512_256_uint64(witness) + quotient = x % len(input) + assert input[quotient] == bytes([rand_elt]).decode() + + +@then( + 'The {result_index}th atomic result for "spin()" satisfies the regex "{regex}"' +) +def spin_results_satisfy(context, result_index, regex): + abi_type = abi.ABIType.from_string("(byte[3],byte[17],byte[17],byte[17])") + result = context.atomic_transaction_composer_return.abi_results[ + int(result_index) + ] + spin, _, _, _ = abi_type.decode(result.raw_value) + spin = bytes(spin).decode() + + assert re.search(regex, spin), f"{spin} did not match the regex {regex}" diff --git a/test_unit.py b/test_unit.py index d1a3aa8d..13c9bcdb 100644 --- a/test_unit.py +++ b/test_unit.py @@ -1261,6 +1261,10 @@ def test_application_address(self): actual = logic.get_application_address(appID) self.assertEqual(actual, expected) + appID = "seventy seven" + with self.assertRaises(AssertionError): + logic.get_application_address(appID) + def test_application_call(self): params = transaction.SuggestedParams(0, 1, 100, self.genesis) for oc in transaction.OnComplete: @@ -2857,254 +2861,6 @@ def test_LogicSigAccount_msig_delegated_different_sender(self): self._test_sign_txn(lsigAccount, sender, expected) -class TestTemplate(unittest.TestCase): - def test_split(self): - addr1 = "WO3QIJ6T4DZHBX5PWJH26JLHFSRT7W7M2DJOULPXDTUS6TUX7ZRIO4KDFY" - addr2 = "W6UUUSEAOGLBHT7VFT4H2SDATKKSG6ZBUIJXTZMSLW36YS44FRP5NVAU7U" - addr3 = "XCIBIN7RT4ZXGBMVAMU3QS6L5EKB7XGROC5EPCNHHYXUIBAA5Q6C5Y7NEU" - s = template.Split( - addr1, addr2, addr3, 30, 100, 123456, 10000, 5000000 - ) - golden = ( - "ASAIAcCWsQICAMDEB2QekE4mAyCztwQn0+DycN+vsk+vJWcsoz/b7NDS6i33HOkvT" - "pf+YiC3qUpIgHGWE8/1LPh9SGCalSN7IaITeeWSXbfsS5wsXyC4kBQ38Z8zcwWVAy" - "m4S8vpFB/c0XC6R4mnPi9EBADsPDEQIhIxASMMEDIEJBJAABkxCSgSMQcyAxIQMQg" - "lEhAxAiEEDRAiQAAuMwAAMwEAEjEJMgMSEDMABykSEDMBByoSEDMACCEFCzMBCCEG" - "CxIQMwAIIQcPEBA=" - ) - golden_addr = ( - "HDY7A4VHBWQWQZJBEMASFOUZKBNGWBMJEMUXAGZ4SPIRQ6C24MJHUZKFGY" - ) - self.assertEqual(s.get_program(), base64.b64decode(golden)) - self.assertEqual(s.get_address(), golden_addr) - sp = transaction.SuggestedParams( - 10000, 1, 100, "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=" - ) - txns = s.get_split_funds_transaction(s.get_program(), 1300000, sp) - golden_txns = base64.b64decode( - "gqRsc2lngaFsxM4BIAgBwJaxAgIAwMQHZB6QTiYDILO3BCfT4PJw36+yT68lZyyjP" - "9vs0NLqLfcc6S9Ol/5iILepSkiAcZYTz/Us+H1IYJqVI3shohN55ZJdt+xLnCxfIL" - "iQFDfxnzNzBZUDKbhLy+kUH9zRcLpHiac+L0QEAOw8MRAiEjEBIwwQMgQkEkAAGTE" - "JKBIxBzIDEhAxCCUSEDECIQQNECJAAC4zAAAzAQASMQkyAxIQMwAHKRIQMwEHKhIQ" - "MwAIIQULMwEIIQYLEhAzAAghBw8QEKN0eG6Jo2FtdM4ABJPgo2ZlZc4AId/gomZ2A" - "aJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCBLA74bTV" - "35FJNL1h0K9ZbRU24b4M1JRkD1YTogvvDXbqJsdmSjcmN2xCC3qUpIgHGWE8/1LPh" - "9SGCalSN7IaITeeWSXbfsS5wsX6NzbmTEIDjx8HKnDaFoZSEjASK6mVBaawWJIylw" - "GzyT0Rh4WuMSpHR5cGWjcGF5gqRsc2lngaFsxM4BIAgBwJaxAgIAwMQHZB6QTiYDI" - "LO3BCfT4PJw36+yT68lZyyjP9vs0NLqLfcc6S9Ol/5iILepSkiAcZYTz/Us+H1IYJ" - "qVI3shohN55ZJdt+xLnCxfILiQFDfxnzNzBZUDKbhLy+kUH9zRcLpHiac+L0QEAOw" - "8MRAiEjEBIwwQMgQkEkAAGTEJKBIxBzIDEhAxCCUSEDECIQQNECJAAC4zAAAzAQAS" - "MQkyAxIQMwAHKRIQMwEHKhIQMwAIIQULMwEIIQYLEhAzAAghBw8QEKN0eG6Jo2Ftd" - "M4AD0JAo2ZlZc4AId/gomZ2AaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt" - "3SABJtkGmjZ3JwxCBLA74bTV35FJNL1h0K9ZbRU24b4M1JRkD1YTogvvDXbqJsdmS" - "jcmN2xCC4kBQ38Z8zcwWVAym4S8vpFB/c0XC6R4mnPi9EBADsPKNzbmTEIDjx8HKn" - "DaFoZSEjASK6mVBaawWJIylwGzyT0Rh4WuMSpHR5cGWjcGF5" - ) - encoded_txns = b"" - for txn in txns: - encoded_txns += base64.b64decode(encoding.msgpack_encode(txn)) - self.assertEqual(encoded_txns, golden_txns) - - def test_HTLC(self): - addr1 = "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM" - addr2 = "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE" - preimage = "cHJlaW1hZ2U=" - hash_image = "EHZhE08h/HwCIj1Qq56zYAvD/8NxJCOh5Hux+anb9V8=" - s = template.HTLC(addr1, addr2, "sha256", hash_image, 600000, 1000) - golden_addr = ( - "FBZIR3RWVT2BTGVOG25H3VAOLVD54RTCRNRLQCCJJO6SVSCT5IVDYKNCSU" - ) - - golden = ( - "ASAE6AcBAMDPJCYDIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IBB2Y" - "RNPIfx8AiI9UKues2ALw//DcSQjoeR7sfmp2/VfIP68oLsUSlpOp7Q4pGgayA5soQ" - "W8tgf8VlMlyVaV9qITMQEiDjEQIxIQMQcyAxIQMQgkEhAxCSgSLQEpEhAxCSoSMQI" - "lDRAREA==" - ) - p = s.get_program() - self.assertEqual(p, base64.b64decode(golden)) - self.assertEqual(s.get_address(), golden_addr) - golden_ltxn = ( - "gqRsc2lngqNhcmeRxAhwcmVpbWFnZaFsxJcBIAToBwEAwM8kJgMg5pqWHm8tX3rIZ" - "geSZVK+mCNe0zNjyoiRi7nJOKkVtvkgEHZhE08h/HwCIj1Qq56zYAvD/8NxJCOh5H" - "ux+anb9V8g/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohMxASIOMRAjEhA" - "xBzIDEhAxCCQSEDEJKBItASkSEDEJKhIxAiUNEBEQo3R4boelY2xvc2XEIOaalh5v" - "LV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5o2ZlZc0D6KJmdgGiZ2jEIH+DsWV/8" - "fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2ZKNzbmTEIChyiO42rPQZmq42un" - "3UDl1H3kZii2K4CElLvSrIU+oqpHR5cGWjcGF5" - ) - sp = transaction.SuggestedParams( - 0, 1, 100, "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=" - ) - ltxn = template.HTLC.get_transaction(p, preimage, sp) - self.assertEqual(golden_ltxn, encoding.msgpack_encode(ltxn)) - - def test_dynamic_fee(self): - addr1 = "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM" - addr2 = "42NJMHTPFVPXVSDGA6JGKUV6TARV5UZTMPFIREMLXHETRKIVW34QFSDFRE" - sp = transaction.SuggestedParams( - 0, 12345, 12346, "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=" - ) - - s = template.DynamicFee(addr1, 5000, sp, addr2) - s.lease_value = base64.b64decode( - "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=" - ) - - golden_addr = ( - "GCI4WWDIWUFATVPOQ372OZYG52EULPUZKI7Y34MXK3ZJKIBZXHD2H5C5TI" - ) - - golden = ( - "ASAFAgGIJ7lgumAmAyD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEyDmm" - "pYeby1feshmB5JlUr6YI17TM2PKiJGLuck4qRW2+SB/g7Flf/H8U7ktwYFIodZd/C" - "1LH6PWdyhK3dIAEm2QaTIEIhIzABAjEhAzAAcxABIQMwAIMQESEDEWIxIQMRAjEhA" - "xBygSEDEJKRIQMQgkEhAxAiUSEDEEIQQSEDEGKhIQ" - ) - p = s.get_program() - self.assertEqual(p, base64.b64decode(golden)) - self.assertEqual(s.get_address(), golden_addr) - sk = ( - "cv8E0Ln24FSkwDgGeuXKStOTGcze5u8yldpXxgrBxumFPYdMJymqcGoxdDeyuM8t6" - "Kxixfq0PJCyJP71uhYT7w==" - ) - txn, lsig = s.sign_dynamic_fee(sk) - - golden_txn = ( - "iqNhbXTNE4ilY2xvc2XEIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5" - "o2ZlZc0D6KJmds0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtk" - "GmibHbNMDqibHjEIH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpo3Jjds" - "Qg/ryguxRKWk6ntDikaBrIDmyhBby2B/xWUyXJVpX2ohOjc25kxCCFPYdMJymqcGo" - "xdDeyuM8t6Kxixfq0PJCyJP71uhYT76R0eXBlo3BheQ==" - ) - golden_lsig = ( - "gqFsxLEBIAUCAYgnuWC6YCYDIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9" - "qITIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTipFbb5IH+DsWV/8fxTuS3BgU" - "ih1l38LUsfo9Z3KErd0gASbZBpMgQiEjMAECMSEDMABzEAEhAzAAgxARIQMRYjEhA" - "xECMSEDEHKBIQMQkpEhAxCCQSEDECJRIQMQQhBBIQMQYqEhCjc2lnxEAhLNdfdDp9" - "Wbi0YwsEQCpP7TVHbHG7y41F4MoESNW/vL1guS+5Wj4f5V9fmM63/VKTSMFidHOSw" - "m5o+pbV5lYH" - ) - self.assertEqual(golden_txn, encoding.msgpack_encode(txn)) - self.assertEqual(golden_lsig, encoding.msgpack_encode(lsig)) - - sk_2 = ( - "2qjz96Vj9M6YOqtNlfJUOKac13EHCXyDty94ozCjuwwriI+jzFgStFx9E6kEk1l4+" - "lFsW4Te2PY1KV8kNcccRg==" - ) - txns = s.get_transactions(txn, lsig, sk_2, 1234) - - golden_txns = ( - "gqNzaWfEQJBNVry9qdpnco+uQzwFicUWHteYUIxwDkdHqY5Qw2Q8Fc2StrQUgN+2k" - "8q4rC0LKrTMJQnE+mLWhZgMMJvq3QCjdHhuiqNhbXTOAAWq6qNmZWXOAATzvqJmds" - "0wOaJnaMQgf4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCCVfq" - "hCinRBXKMIq9eSrJQIXZ+7iXUTig91oGd/mZEAqJsds0wOqJseMQgf4OxZX/x/FO5" - "LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjcmN2xCCFPYdMJymqcGoxdDeyuM8t6Kxix" - "fq0PJCyJP71uhYT76NzbmTEICuIj6PMWBK0XH0TqQSTWXj6UWxbhN7Y9jUpXyQ1xx" - "xGpHR5cGWjcGF5gqRsc2lngqFsxLEBIAUCAYgnuWC6YCYDIP68oLsUSlpOp7Q4pGg" - "ayA5soQW8tgf8VlMlyVaV9qITIOaalh5vLV96yGYHkmVSvpgjXtMzY8qIkYu5yTip" - "Fbb5IH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpMgQiEjMAECMSEDMAB" - "zEAEhAzAAgxARIQMRYjEhAxECMSEDEHKBIQMQkpEhAxCCQSEDECJRIQMQQhBBIQMQ" - "YqEhCjc2lnxEAhLNdfdDp9Wbi0YwsEQCpP7TVHbHG7y41F4MoESNW/vL1guS+5Wj4" - "f5V9fmM63/VKTSMFidHOSwm5o+pbV5lYHo3R4boujYW10zROIpWNsb3NlxCDmmpYe" - "by1feshmB5JlUr6YI17TM2PKiJGLuck4qRW2+aNmZWXOAAWq6qJmds0wOaJnaMQgf" - "4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCCVfqhCinRBXKMIq" - "9eSrJQIXZ+7iXUTig91oGd/mZEAqJsds0wOqJseMQgf4OxZX/x/FO5LcGBSKHWXfw" - "tSx+j1ncoSt3SABJtkGmjcmN2xCD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclW" - "lfaiE6NzbmTEIIU9h0wnKapwajF0N7K4zy3orGLF+rQ8kLIk/vW6FhPvpHR5cGWjc" - "GF5" - ) - - actual = base64.b64decode( - encoding.msgpack_encode(txns[0]) - ) + base64.b64decode(encoding.msgpack_encode(txns[1])) - self.assertEqual(golden_txns, base64.b64encode(actual).decode()) - - def test_periodic_payment(self): - addr = "SKXZDBHECM6AS73GVPGJHMIRDMJKEAN5TUGMUPSKJCQ44E6M6TC2H2UJ3I" - s = template.PeriodicPayment(addr, 500000, 95, 100, 1000, 2445756) - s.lease_value = base64.b64decode( - "AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg=" - ) - - golden_addr = ( - "JMS3K4LSHPULANJIVQBTEDP5PZK6HHMDQS4OKHIMHUZZ6OILYO3FVQW7IY" - ) - - golden = ( - "ASAHAegHZABfoMIevKOVASYCIAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCAECAwQFB" - "gcIIJKvkYTkEzwJf2arzJOxERsSogG9nQzKPkpIoc4TzPTFMRAiEjEBIw4QMQIkGC" - "USEDEEIQQxAggSEDEGKBIQMQkyAxIxBykSEDEIIQUSEDEJKRIxBzIDEhAxAiEGDRA" - "xCCUSEBEQ" - ) - p = s.get_program() - self.assertEqual(p, base64.b64decode(golden)) - self.assertEqual(s.get_address(), golden_addr) - gh = "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=" - sp = transaction.SuggestedParams(0, 1200, None, gh) - - ltxn = s.get_withdrawal_transaction(p, sp) - golden_ltxn = ( - "gqRsc2lngaFsxJkBIAcB6AdkAF+gwh68o5UBJgIgAQIDBAUGBwgBAgMEBQYHCAECA" - "wQFBgcIAQIDBAUGBwggkq+RhOQTPAl/ZqvMk7ERGxKiAb2dDMo+SkihzhPM9MUxEC" - "ISMQEjDhAxAiQYJRIQMQQhBDECCBIQMQYoEhAxCTIDEjEHKRIQMQghBRIQMQkpEjE" - "HMgMSEDECIQYNEDEIJRIQERCjdHhuiaNhbXTOAAehIKNmZWXNA+iiZnbNBLCiZ2jE" - "IH+DsWV/8fxTuS3BgUih1l38LUsfo9Z3KErd0gASbZBpomx2zQUPomx4xCABAgMEB" - "QYHCAECAwQFBgcIAQIDBAUGBwgBAgMEBQYHCKNyY3bEIJKvkYTkEzwJf2arzJOxER" - "sSogG9nQzKPkpIoc4TzPTFo3NuZMQgSyW1cXI76LA1KKwDMg39flXjnYOEuOUdDD0" - "znzkLw7akdHlwZaNwYXk=" - ) - self.assertEqual(golden_ltxn, encoding.msgpack_encode(ltxn)) - - def test_limit_order_a(self): - addr = "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM" - s = template.LimitOrder(addr, 12345, 30, 100, 123456, 5000000, 10000) - - golden_addr = ( - "LXQWT2XLIVNFS54VTLR63UY5K6AMIEWI7YTVE6LB4RWZDBZKH22ZO3S36I" - ) - - golden = ( - "ASAKAAHAlrECApBOBLlgZB7AxAcmASD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJ" - "clWlfaiEzEWIhIxECMSEDEBJA4QMgQjEkAAVTIEJRIxCCEEDRAxCTIDEhAzARAhBR" - "IQMwERIQYSEDMBFCgSEDMBEzIDEhAzARIhBx01AjUBMQghCB01BDUDNAE0Aw1AACQ" - "0ATQDEjQCNAQPEEAAFgAxCSgSMQIhCQ0QMQcyAxIQMQgiEhAQ" - ) - p = s.get_program() - - self.assertEqual(p, base64.b64decode(golden)) - self.assertEqual(s.get_address(), golden_addr) - - sk = ( - "DTKVj7KMON3GSWBwMX9McQHtaDDi8SDEBi0bt4rOxlHNRahLa0zVG+25BDIaHB1dS" - "oIHIsUQ8FFcdnCdKoG+Bg==" - ) - gh = "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=" - sp = transaction.SuggestedParams(10, 1234, 2234, gh) - [stx_1, stx_2] = s.get_swap_assets_transactions(p, 3000, 10000, sk, sp) - golden_txn_1 = ( - "gqRsc2lngaFsxLcBIAoAAcCWsQICkE4EuWBkHsDEByYBIP68oLsUSlpOp7Q4pGgay" - "A5soQW8tgf8VlMlyVaV9qITMRYiEjEQIxIQMQEkDhAyBCMSQABVMgQlEjEIIQQNED" - "EJMgMSEDMBECEFEhAzAREhBhIQMwEUKBIQMwETMgMSEDMBEiEHHTUCNQExCCEIHTU" - "ENQM0ATQDDUAAJDQBNAMSNAI0BA8QQAAWADEJKBIxAiEJDRAxBzIDEhAxCCISEBCj" - "dHhuiaNhbXTNJxCjZmVlzQisomZ2zQTSomdoxCB/g7Flf/H8U7ktwYFIodZd/C1LH" - "6PWdyhK3dIAEm2QaaNncnDEIKz368WOGpdE/Ww0L8wUu5Ly2u2bpG3ZSMKCJvcvGA" - "pTomx2zQi6o3JjdsQgzUWoS2tM1RvtuQQyGhwdXUqCByLFEPBRXHZwnSqBvgajc25" - "kxCBd4Wnq60VaWXeVmuPt0x1XgMQSyP4nUnlh5G2Rhyo+taR0eXBlo3BheQ==" - ) - golden_txn_2 = ( - "gqNzaWfEQKXv8Z6OUDNmiZ5phpoQJHmfKyBal4gBZLPYsByYnlXCAlXMBeVFG5CLP" - "1k5L6BPyEG2/XIbjbyM0CGG55CxxAKjdHhuiqRhYW10zQu4pGFyY3bEIP68oLsUSl" - "pOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITo2ZlZc0JJKJmds0E0qJnaMQgf4OxZX/" - "x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGmjZ3JwxCCs9+vFjhqXRP1sNC/MFLuS" - "8trtm6Rt2UjCgib3LxgKU6Jsds0IuqNzbmTEIM1FqEtrTNUb7bkEMhocHV1Kggcix" - "RDwUVx2cJ0qgb4GpHR5cGWlYXhmZXKkeGFpZM0wOQ==" - ) - - self.assertEqual(encoding.msgpack_encode(stx_1), golden_txn_1) - self.assertEqual(encoding.msgpack_encode(stx_2), golden_txn_2) - - class TestDryrun(dryrun.DryrunTestCaseMixin, unittest.TestCase): def setUp(self): self.mock_response = dict(error=None, txns=[]) @@ -4061,6 +3817,13 @@ def test_array_static_encoding(self): "00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 03" ), ), + ( + ArrayStaticType(ByteType(), 17), + [0, 0, 0, 0, 0, 0, 4, 3, 31, 0, 0, 0, 0, 0, 0, 0, 33], + bytes.fromhex( + "00 00 00 00 00 00 04 03 1f 00 00 00 00 00 00 00 21" + ), + ), ] for test_case in test_cases: @@ -4305,7 +4068,6 @@ def test_contract(self): TestLogicSig, TestLogicSigAccount, TestLogicSigTransaction, - TestTemplate, TestDryrun, TestABIType, TestABIEncoding,